Zygote is a very important process in Android, it and Init process, SystemServer process in Android has an irreplaceable position.

ZygoteIntroduction to the

Linux processes are generated by system call fork. The fork child processes share memory images with the parent process except some core data structures in the kernel. The operating system assigns a new page to the child only when it needs to overwrite the shared memory. This is called Copy On Write

Typically, when a child process is forked, the system call exec continues. Exec replaces the code segment, data segment, heap, and stack of the current process with the contents of a new executable file.

Fork and exec is standard Linux practice for starting applications, as is the Init process for starting various services.

However, Zygote created the application using only fork, without calling exec

First of all, Android applications run on virtual machines, and it is the Java code in virtual machines that makes the difference between applications, but the basic operating environment is the same.

Second, Zygote creates a virtual machine during initialization, loading the required system libraries and resource files into memory. When Zygote forks a child, the child gets a VIRTUAL machine that has already loaded the basic resources.

In this way, the next step is to load apK-related resources to run, which can improve efficiency

ZygoteInitialization of the process

Zygote is started as a service in Init. Let’s take a look at its configuration in init.rc:

import /init.${ro.zygote}.rc on late-init ...... Zygote trigger Zygote-start on zygote-start && # Now we can start zygote for devices with file based encryption # Start netd start zygote start zygote_secondaryCopy the code

Starting with Android 5.0, Android will support 64-bit compilation, and Zygote itself will have 32-bit and 64-bit differences. Therefore, the ro.zygote attribute controls the startup of different versions of the Zygote process. In the 9.0 source code, there are four related files:

init.zygote32.rc
init.zygote32_64.rc
init.zygote64.rc
init.zygote64_32.rc
Copy the code

Pick a particular one (init.zygote32_64.rc) :

service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    ......
service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    ......
Copy the code
  • You can see from the file contents that two are definedZygoteServices:zygoteandzygote_secondary
  • The biggest difference between the two services isExecutable fileDifferent: One isapp_process32, and the other aapp_process64
  • init.zygote64_32.rcI won’t post the file, but it’s just twoExecutable fileDiscuss the
  • forinit.zygote32.rcandinit.zygote64.rcThere’s only one of themZygoteservice

From here we can see that Android will support four operating modes:

  • Pure 32-bit mode:ro.zygoteThe value ofzygote32
  • 32-bit mode is dominant, 64-bit mode is secondary:ro.zygoteThe value ofzygote32_64
  • Pure 64-bit mode:ro.zygoteThe value ofzygote64
  • 64-bit mode is dominant, 32-bit mode is secondary:ro.zygoteThe value ofzygote64_32

For the executable app_process, the source path is in frameworks/base/ CMDS /app_process, but let’s take a look at the app_process usage before we look at the source

app_processThe use of the

We know that Zygote is started by app_process

App_process parses startup parameters and selects different startup modes based on the parameters

To better understand app_process, let’s look at how it is used. The app_process parameter is described as follows:

fprintf(stderr,
        "Usage: app_process [java-options] cmd-dir start-class-name [options]\n");
Copy the code

A little too neat…

Let’s take a look at some examples, using Zygote as an example:

 /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
Copy the code

Here’s an interesting version of the following directive: Run an Application containing the main function class named Test:

app_process -Djava.class.path=/data/local/tmp/Test.dex /data/lee Test
Copy the code

Combined with the source Usage(pure version)… And these two examples, we can sum them up like this:

  • -Xzygoteand-Djava.class.p**: belong to[java-options], these parameters are passed toThe virtual machine, and the parameter must be-At the beginning, once you encounter the non-or--Said,[java-options]The end of the
  • /system/binand/data/lee: belong tocmd-dir, the program run directory, optionally specified, file operation will be the current path, most of the serious run in/system/binUnder the
  • Test: belong tostart-class-nameDeclare the name of the entry class, plus the package name if there is one
  • --zygote: belong to[options], these parameters are signed--At the beginning. parameter--zygoteTo startZygoteprocess

app_processthemain()

With the use of app_process in mind above, it will be easier to analyze the main function, as usual, looking at the whole:

int main(int argc, char* const argv[])
{
    1. Create an AppRuntime object
    
    // 2. Save the java-option parameter

    // 3. Parse the Option argument
    
    // 4. Prepare ZygoteInit or RuntimeInit parameters according to the result of parameter parsing
    
    // 5. Change the process name to the string specified by nice-name
    
    // 6. Start the Corresponding Java class based on the parsed result
}
Copy the code

Very clear ah, we look at the specific source code

createAppRuntimeobject

The main () the source code:

AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
Copy the code
  • AppRuntimeIs in theapp_processClass defined in theAndroidRuntimeClass.
  • AndroidRuntimeClass is a very important class at the bottom. It is mainly used to create and initialize virtual machines
  • The wholemain()The rest of the process goes through thisruntimeTo operate the

saveJava-Optionparameter

.for (i = 0; i < argc; i++) {
        // omit the know_command correlation.if (argv[i][0] != The '-') { 
            // If it does not start with -, parsing ends
            // Follow the app_process argument rules: java-option is followed by cmd-dir instead of starting with -
            // Therefore, normal parsing to Java-option will stop
            break;
        }
        if (argv[i][1] = =The '-' && argv[i][2] = =0) {
            // If it is -- and followed by a null character, parsing ends
            // I don't know what this is for
            ++i; // Skip --.
            break;
        }
        runtime.addOption(strdup(argv[i]));
        // Omit the printed information. }...Copy the code

parsingOptionparameter

The Option argument is called the Runtime argument in the source code

    // All the parameters that need to be parsed are here
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;
    
    Parent dir = parent dir = parent dir = parent dir = parent dir = parent dir
    // No extra processing is required for CMD dir, just skip it
    ++i;  // Skip unused "parent dir" argument.
    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) {
            // If it does not start with --, it is class name
            className.setTo(arg);
            break;
        } else {
            --i;
            break; }}Copy the code

To prepareZygoteInitorRuntimeInitRequired parameters

    // Define a collection of character arguments
    Vector<String8> args;
    if(! className.isEmpty()) {// We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        // Not Zygote mode, not deleted official comment, happy not
        // The Remainder of args get passed to startup class main(). Make
        // copies of them before we overwrite them with the process name.
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
        // Omit the print part. }else {
        // We're in zygote mode.
        maybeCreateDalvikCache();
        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }
        // Omit the fetch of ABI attribute values.String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);
        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        // In Zygote mode, the parameters of app_process are uniformly packed into the parameter set
        for(; i < argc; ++i) { args.add(String8(argv[i])); }}Copy the code

Change the process name tonice-nameSpecified string

    if(! niceName.isEmpty()) {// This is a function provided by AndroidRuntime to set the process name
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }
    
    / / in AndroidRuntime. CPP
    void AndroidRuntime::setArgv0(const char* argv0, bool setProcName) {
        if (setProcName) {
            int len = strlen(argv0);
            if (len < 15) {
                pthread_setname_np(pthread_self(), argv0);
            } else {
                pthread_setname_np(pthread_self(), argv0 + len - 15); }}memset(mArgBlockStart, 0, mArgBlockLength);
        strlcpy(mArgBlockStart, argv0, mArgBlockLength);
    }
Copy the code

Start the correspondingJavaclass

    if (zygote) {
        // If it is --zygote, execute the ZygoteInit class to start the zygote process
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        // If class name is specified, the class passed in is executed through RuntimeInit
        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.");
    }
Copy the code

app_processinAndroidThe application of

In addition to starting the Zygote process, app_process can also be used to execute a system’s Java classes

A good example is the common Android tool AM:

Am is a tool for sending intents, such as AM Start and AM broadcast.

But AM is really just a script file with a few lines of code, all of which is done by calling app_process

#! /system/bin/shif [ "$1"! ="instrument"]; then cmd activity"$@"
else
    base=/system
    export CLASSPATH=$base/framework/am.jar
    exec app_process $base/bin com.android.commands.am.Am "$@"
fi
Copy the code

No, no, Android is a surprise, ha ha ha!

We can carefully read the Am. Java class, very characteristic, call relationship is really drop trouble god, refuel yo!

Start a VMAndroidRuntimeclass

When analyzing main() of app_process, we know that AppRuntime inherits the AndroidRuntime class.

  • AndroidRuntimeClass is an important class that is responsible for startingThe virtual machineAs well asJava thread
  • AndroidRuntimeClass has only one instance object in a process, stored in a global variablegCurRuntimeIn the

The constructor

AndroidRuntime class constructors are as follows:

AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
        mExitWithoutCleanup(false),
        mArgBlockStart(argBlockStart),
        mArgBlockLength(argBlockLength)
{
    // Initialize the SKia graphics system
    SkGraphics::Init();
    // Pre-allocate enough space to hold a fair number of options.
    mOptions.setCapacity(20);
    // Can only be initialized once
    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this;
}
Copy the code

Starting a VM

This chapter is only a brief introduction to the virtual machine startup process. The implementation details will be included in a separate chapter

In the main() function of app_process, runtime.start() is finally called to execute the Java class. The start() function is defined in AndroidRuntime. The code is quite long, so let’s look at the overall flow first:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    // 1. Print the startup log
    
    // 2. Obtain system directory

    // 3. Start the VM

    // 4. Call onVmCreated
    
    // 5. Register system JNI functions
    
    // 6. Prepare parameters for the Main function of the Java class
    
    // 7. Call the main method of ZygoteInit

}
Copy the code

The print startLog

Source code snippet:

ALOGD(">>>>>> START %s uid %d <<<<<<\n", className ! =NULL ? className : "(unknown)", getuid());
Copy the code

Let’s take a look at the Log fragment of the system boot process:

00:00:21. 226808 3244 3244 D AndroidRuntime: > > > > > > START com. Android. Internal. OS. ZygoteInit uid 0 < < < < < <Copy the code
  • This period ofLogMark theAndroidSystem startup
  • Because the next application process is fromZygoteprocessforkIt’s coming out, so it’s not going to be executedstart()The function
  • ifAndroidThe system oflogRepeatedly appear in this section of content, while outputIDAre allZygote, it indicates that the system may be faulty.ZygoteThe process is constantly being restarted

Obtaining system directory

The source code snippet

    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if(! hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }
Copy the code
  • System directory from environment variablesANDROID_ROOTread
  • If it is not set, the default value is/system
    • The system directory is inInitCreated in the process
  • if/systemThe directory does not exist. Exit directly

Starting a VM

The source code snippet

    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if(startVm(&mJavaVM, &env, zygote) ! =0) {
        return;
    }
Copy the code

Start the VIRTUAL machine using startVm, which we’ll look at later.

callonVmCreatedfunction

    onVmCreated(env);
Copy the code

The onVmCreated function is a virtual function that actually calls an overloaded function of AppRuntime for app_process. Here is the code logic in AppRuntime:

    virtual void onVmCreated(JNIEnv* env)
    {
        if (mClassName.isEmpty()) {
            return; // Zygote. Nothing to do here.
        }
        // Omit some interesting comments related to class loading.char* slashClassName = toSlashClassName(mClassName.string());
        mClass = env->FindClass(slashClassName);
        if (mClass == NULL) {
            ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
        }
        free(slashClassName);
        mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
    }
Copy the code

In AppRuntime’s onVmCreated function:

  • If it isZygoteProcess, variablemClassNameThe value of the willnullWill return immediately
  • If it’s an ordinaryJava classesThe call,mClassNameWill hold the name of the class
  • toSlashClassName(mClassName.string())The role of theThe name of the classConverted to classFully qualified name
    • The name of the classlikecom.android.ZygoteintoFully qualified namebecomes/com/android/Zygote

Register the system’s JNI functions

The source code snippet

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

StartReg registers JNI native functions in the global array gRegJNI with the virtual machine by calling register_jni_procs:

/* * Register android native functions with the VM. */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{...// This function modifies the gCreateThreadFn function pointer to javaCreateThreadEtc()
    // After that, the new child thread will
    JavaCreateThreadEtc () -> javaThreadShell () -> AttachThread() -> JVM ->AttachCurrentThread()
    // The normal C layer thread becomes the JVM threadandroidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); .if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return - 1; }... }Copy the code

For the gRegJNI array, the format is

static const RegJNIRec gRegJNI[] = {
    ......
    REG_JNI(register_android_util_Log),
    ......
    REG_JNI(register_android_os_Binder),
    ......
    REG_JNI(register_android_graphics_Paint),
    ......
    REG_JNI(register_android_app_Activity),
    ......
};
Copy the code

Many elements are omitted, and each member of the array represents a JNI mapping of a class of files, where REG_JNI is a macro definition that calls the corresponding JNI registration method

Prepare parameters for the Main function of the Java class

The source code snippet

    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String"); assert(stringClass ! =NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL); assert(strArray ! =NULL); 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);
    }
Copy the code

The above is a very classic operation to create Java layer objects in Native layer:

  • To create ajava.lang.StringArray object of
  • The array object is assigned element by element based on the parameters passed in

Call the main method of the ZygoteInit class

The source code snippet

    // Convert to fully qualified name
    char* slashClassName = toSlashClassName(className ! =NULL ? className : "");
    // Get the class object of the class
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        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 main
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
    // The main function is finished
    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");
Copy the code

Before calling main:

  • Through the firstGetStaticMethodIDTo obtainmainmethodsID
  • Then use theCallStaticVoidMethodTo invoke theJavaLayer of the function

At this point, the Zygote process initialization moves to the Java layer, remember the startup logic in the main() function of app_process?

To recap:

    if (zygote) {
        // If it is --zygote, execute the ZygoteInit class to start the zygote process
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        // If class name is specified, the class passed in is executed through RuntimeInit
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    }
Copy the code

Next, let’s look at the Zygoteinit.java class

Initialization work –ZygoteInitclass

The ZygoteInit class is responsible for initializing the Java layer of the Zygote process. The entry method main() has the following code (more detailed comments) :

    public static void main(String argv[]) {
        // Create Zygote service management class to register socket listener
        ZygoteServer zygoteServer = new ZygoteServer();
        // Mark zygote start. This ensures that thread creation will throw
        // an error.
        // After this method is called, the virtual machine rejects the creation of the thread, which will generate an error
        ZygoteHooks.startZygoteNoThreadCreation();
        // Zygote goes into its own process group.
        Os.setpgid(0.0);
        / /...
        final Runnable caller;
        try{... Omit print correlation// Start the DDMS VM monitoring debugging service
            RuntimeInit.enableDdms();
            
            // Parameter parsing
            boolean startSystemServer = false;
            String socketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    // Start system services
                    startSystemServer = true;
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    // Start lazy loading
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    // Abi type, one CPU corresponds to one ABI
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    // Parse the socket name
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    // Unknown parameters throw an exception
                    throw new RuntimeException("Unknown command line argument: "+ argv[i]); }}// An exception will be thrown if ABI parameters are not specified
            if (abiList == null) {
                throw new RuntimeException("No ABI list supplied.");
            }
            // Register Zygote's socket listener port to receive messages for starting applications
            zygoteServer.registerServerSocketFromEnv(socketName);
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if(! enableLazyPreload) {// Lazy loading is not enabled. Omit some log prints// Perform preloading operations, including system preloading classes, Framework resources, and openGL resourcespreload(bootTimingsTraceLog); . Omit some log print}else {
                // The priority of the reset thread in case of lazy loading
                Zygote.resetNicePriority();
            }
            // Do an initial gc to clean up after startup. Omit log print// Official note: Run several specified GC's, try to clean up generations of soft references and reachable objects, among other garbage
            // This method works only before fork()gcAndFinalize(); . Omit log print// Some security-related initialization operations
            Zygote.nativeSecurityInit();
            // Zygote process unmounts root storage spaces.
            // The purpose is to unmount the entire storage directory /storage and mount the temporary directory instead
            // This action is related to Android's sandbox (isolated storage)
            Zygote.nativeUnmountStorageOnInit();
            / / echo the front startZygoteNoThreadCreation () method
            // Inform the virtual machine that threads can now be created in the Zygote process
            ZygoteHooks.stopZygoteNoThreadCreation();
            if (startSystemServer) {
                // fork SystemServer
                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 is empty, the zygote process is running
                if(r ! =null) {
                    // if r is not empty, it is the incubated child process SystemServer
                    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.
            // This part is executed in zygote process
            // Enter an infinite loop to process the data received by the Zygote socket
            caller = zygoteServer.runSelectLoop(abiList);
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            // Note that this section is mainly for child processes
            // There is no need to have zygote service in the child process
            // So the shutdown is theoretically to shut down the useless Zygote service in the child process
            zygoteServer.closeServerSocket();
        }
        // We're in the child process and have exited the select loop. Proceed to execute the
        // command.
        // All processes except systemServer actually start the main function here
        RunSelectLoop is a wireless loop. How can I get around it?
        // Why does it have to be a child process?
        if(caller ! =null) { caller.run(); }}Copy the code

After reading the main function, you must have a lot of questions, because of the child process, the code processing logic is a bit complicated

Now I will learn the process of doubt and understanding to share with you

Knowledge:forkThe results of the

For fork, we need to remember:

  • forkA new process is created that is identical to the current process
  • inforkAfter the function call
    • A new process will start
    • And with the current process fromforkThe function returns
  • aboutforkThe return value of the
    • New process returns0
    • The current process returns the value of the new processpid

Question 2:systemserverThe launch of the

            if (startSystemServer) {
                // fork SystemServer
                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 is empty, the zygote process is running
                if(r ! =null) {
                    // if r is not empty, it is the incubated child process SystemServer
                    r.run();
                    return; }}Copy the code

ForkSystemServer this code implements forkSystemServer forking with forkSystemServer logic:

  • Run pid = zygote. forkSystemServer

    • Note that two processes are generated after execution:zygoteandsystemserver
    • Also, both processes start execution at the same time, but the process receivespidIt’s different
    • Refer to the previous oneknowledge
  • Pid judgment:

        // For systemServer processes, pid is 0
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
            zygoteServer.closeServerSocket();
            / / handleSystemServerProcess this function will be:
            // Find SystemServer's main function by reflection
            // wrap it in Runnable's run function
            return handleSystemServerProcess(parsedArgs);
        }
        // pid does not equal 0. The parent process, zygote, returns null
        return null;
    Copy the code

At this point, you should understand the following code processing meaning:

        if(r ! =null) {
            r.run();
            return;
        }
Copy the code

Question 3:mainLast of the functioncaller.run()How does it work

The application should be launched through this caller.run(), and there’s no problem figuring it out

    public static void main(String argv[]) {...try{... caller = zygoteServer.runSelectLoop(abiList); }finally{ zygoteServer.closeServerSocket(); }...if(caller ! =null) { caller.run(); }}Copy the code

The official comment already says caller.run(); Is the child really run, but zygoteServer. RunSelectLoop not a wireless loop, zha of?

Let’s look at the runSelectLoop function:

    Runnable runSelectLoop(String abiList) {...while (true) {...try {
            ZygoteConnection connection = peers.get(i);
            final Runnable command = connection.processOneCommand(this);
            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.. }}... }Copy the code

Two important points:

  • amIsForkChildattribute
    • whenmIsForkChildProperties fortrue, will exit the loop
    • And return aRunnableobject
  • aprocessOneCommandfunction

For the processOneCommand function, it also has an important operation:

Runnable processOneCommand(ZygoteServer zygoteServer) {...// fork the child processpid = Zygote.forkAndSpecialize(...... ;if (pid == 0) {
            // in child
            // Set mIsForkChild to truezygoteServer.setForkChild(); zygoteServer.closeServerSocket(); .// handleChildProc This function will:
            // Use reflection to find the main function that starts the class
            // wrap it in Runnable's run function
            returnhandleChildProc(......) ; }else{...return null; }}Copy the code

In Zygote’s infinite loop, when listening for data:

  • throughprocessOneCommandfunctionforkOut of theThe child processAt this time:
    • Two processes are running:zygoteandThe child process
  • Still based onpidTo determine which one isThe child process
    • forThe child processTo set upmIsForkChildProperties fortrue
    • andThe child processPack upRunnableThe object returned
    • forzygoteTo return tonull

The runSelectLoop() in the child stops the loop and executes to caller.run().

The Android 5.0 source code executes caller.run() by throwing an exception

With that in mind, it should be easy for zygote to start the application

ZygoteStart the application

We already know that Zygote performs the task of starting the application by using runSelectLoop() as a daemon. Take a look at the sequence diagram of the process and explain each step in detail

Register the Zygote socket

The ZygoteInit class’s main() function

  • First callZygoteServertheregisterServerSocketFromEnvTo create a localsocket
  • Then callrunSelectLoopCome in and waitsocketConnected loops

RegisterServerSocketFromEnv () function is as follows:

    void registerServerSocketFromEnv(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            // The fullSocketName is ANDROID_SOCKET_zygote
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                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);
                mServerSocket = new LocalServerSocket(fd);
                mCloseSocketFd = true;
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex); }}}Copy the code

The ANDROID_SOCKET_zygote function retrieves fileDesc of the socket using the environment variable ANDROID_SOCKET_zygote.

  • thisFileDesc (File descriptor)How did it come about? So let’s go back a little bitinit.rcContents of:
    service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote ......  socket zygote stream 660 root system ......Copy the code
  • ZygoteOne is specified when the service startsoptionUsed forsocketThe creation of
    • InitThe process uses this option to create one for local communicationsocket
    • And to bring thesocketthefileDescPut it in an environment variableANDROID_SOCKET_zygoteIn the
    • The following part of the environment variable stringzygoteisoptionThe name of the
  • getfileDescLater, byLocalServerSocket()Create a localsocketService and save to a global variablemServerSocketIn the

Requesting to start the application

Android starts a new process in ActivityManagerService. There are many reasons to start a new process in ActivityManagerService by calling startProcess() :

final String entryPoint = "android.app.ActivityThread";
private ProcessStartResult startProcess(String hostingType, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime) {...final ProcessStartResult startResult;
        if (hostingType.equals("webview_service")) {... }else {
            startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                    app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                    app.info.dataDir, invokeWith,
                    newString[] {PROC_START_SEQ_IDENT + app.startSeq}); }... }Copy the code

As you can see, the key function is process.start (). And Process. The start () the first parameter to the entryPoint is to start after the execution of Java classes: android. App. ActivityThread

What parameters are sent to Zygote when the application is launched? The application startup process will be expanded later

Let’s trace process.start ():

// class:Process
public static final ProcessStartResult start(final String processClass ......) {
        returnzygoteProcess.start(processClass......) ; }// class:ZygoteProcess
public final Process.ProcessStartResult start(final String processClass ......) {
        returnstartViaZygote(processClass ......) ; }private Process.ProcessStartResult startViaZygote(final String processClass ......){
        ArrayList<String> argsForZygote = new ArrayList<String>();

        // --runtime-args, --setuid=, --setgid=,
        // and --setgroups= must go first
        argsForZygote.add("--runtime-args"); . argsForZygote.add(processClass);return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
Copy the code

A little bit too many parameters, code part meaning ha, heh heh

  • start()The method callstartViaZygote()Method to start the application
  • startViaZygote()Save the startup parameters of the application process toargsForZygoteIn the collection
  • And then callzygoteSendArgsAndGetResultMethod to send the startup parameters of the application processZygoteprocess
  • zygoteSendArgsAndGetResult()Method is calledopenZygoteSocketIfNeeded()Method is used to create asocket, the code looks like this:
    public static final String ZYGOTE_SOCKET = "zygote";
    public static final LocalSocketAddress address = new LocalSocketAddress(ZYGOTE_SOCKET, LocalSocketAddress.Namespace.RESERVED);
    final LocalSocket zygoteSocket = new LocalSocket();
    zygoteSocket.connect(address);
    Copy the code
    • forLocal SocketUsing a string as an address can communicate, very convenient
  • socketOnce the connection is established,zygoteSendArgsAndGetResultTake advantage of thissocketWrite startup parameters:
    writer.write(Integer.toString(args.size()));
    writer.newLine();
    for (int i = 0; i < sz; i++) {
        String arg = args.get(i);
        writer.write(arg);
        writer.newLine();
    }
    writer.flush();
    Copy the code

At this point, the parameters requesting application startup are sent to the Zygote process. Let’s take a look at the Zygote process

Process requests to start applications

The Zygote process uses runSelectLoop() to listen for and process requests to start applications.

    Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
        // Add the socket zygote file descriptor to the FDS set
        Note here that mServerSocket is the first element in the collection
        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);
        while (true) {
            // Dynamically generate a pollFds array as a poll listener
            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 {
                // Event listening for files in the pollFds array via poll
                // -1 blocks until data comes in
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            // This will be executed when data arrives
            for (int i = pollFds.length - 1; i >= 0; --i) {
                // Loop over to find the file that received the data
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    // if I =0, it is mServerSocket
                    // Indicates that the client initiated a connection request
                    // Following the LocalServerSocket process, a LocalSocket needs to be created for client communication
                    // acceptCommandPeer() creates the ZygoteConnection object according to this process
                    // The real communication object is LocalSocket
                    // Then add the FD of the communication object to the FDS collection
                    // Don't bother with this part of the processing method, as LocalSocket standard usage can be
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    try {
                        // This is the real processing part
                        ZygoteConnection connection = peers.get(i);
                        // Execute the processOneCommand function corresponding to connection
                        // The function reads the parsed parameters and performs operations related to the child process fork
                        final Runnable command = connection.processOneCommand(this);

                        if (mIsForkChild) {
                            ......
                            // Exit the selectLoop loop
                            return command;
                        } else{...// the zygote process is closed to remove connection
                            if(connection.isClosedByPeer()) { connection.closeSocket(); peers.remove(i); fds.remove(i); }}}catch (Exception e) {
                        if(! mIsForkChild) {// Clear the exception. ZygoteConnection conn = peers.remove(i); conn.closeSocket(); fds.remove(i); }else{... }}finally{... mIsForkChild =false;
                    }
                }
            }
        }
    }
Copy the code

We know from the above code, real processing for news is in ZygoteConnection processOneCommand function, we have to focus on:

processOneCommandfunction

The function is divided into three parts:

  • Parsing startup parameters
  • The child processfork
  • The child process is initialized

Let’s take a look at each of them

Parsing startup parameters

The flow of parameter resolution is

  • Through the firstreadArgumentListMethods fromsocketMultiple parameter rows are read from the connection
    • The style of the argument line is--setuid=1
    • Between the lines\r,\nor\r\nsegmentation
  • Call it after readingArgumentsOf the classparseArgsMethod parses the data into a list of parameters
    • Specific parameter meaning, you can refer toArgumentsClass notes

After parsing the parameters, processOneCommand also checks and sets the parsed parameters:

. Omit some parameter attribute judgments// Check that the client has the permission to specify the user ID and group ID of the process
        // If the process is root, you can specify any process
        // If it is the sys process, this parameter can be specified when the value of ro.factorytest is > 0
        applyUidSecurityPolicy(parsedArgs, peer);
        // Determine whether invoke-with execution permission is available
        applyInvokeWithSecurityPolicy(parsedArgs, peer);
        // If ro.debuggable is 1, start JDWP protocol
        applyDebuggerSystemProperty(parsedArgs);
        // Handle the invoke-with attribute
        applyInvokeWithSystemProperty(parsedArgs);
Copy the code

I won’t go into the details

forkThe child process

Once the parameters are checked, the processOneCommand function calls the ForkandWpecte method to fork the child process:

    pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
            parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
            parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
            parsedArgs.instructionSet, parsedArgs.appDataDir);
Copy the code

ForkAndSpecialize finally is done through native layer ForkAndSpecializeCommon fork, we simply introduce the main work of the function:

  • throughforkCreating a child process
  • Mount in child processemulate storage
  • Set the user ID, group ID, and group to which the process belongs in the child process
  • Passes in the child processsetrlimitThe system call sets the system resource limit for the process
  • Passes in the child processcapsetThe system call sets the permissions of the process
  • Passes in the child processselinux_android_setcontextSet the security context for the application process

After returning to the Java layer from the Native layer

  • ifpid==0Is in a child processhandleChildProcfunction
  • ifpid! = 0Is in the Zygote processhandleParentProcFunction, and returnsnull

This part of the code has been shown in the previous question:

    if (pid == 0) {
        // in child
        zygoteServer.setForkChild();
        zygoteServer.closeServerSocket();
        IoUtils.closeQuietly(serverPipeFd);
        serverPipeFd = null;
        return handleChildProc(parsedArgs, descriptors, childPipeFd,parsedArgs.startChildZygote);
    } else {
        // In the parent. A pid < 0 indicates a failure and will be handled in
        // handleParentProc.
        IoUtils.closeQuietly(childPipeFd);
        childPipeFd = null;
        handleParentProc(pid, descriptors, serverPipeFd);
        return null;
    }
Copy the code

Initialization of the child process

After the Zygote process forks the child process, it calls the handleChildProc method to initialize the child process.

  • handleChildProcMethod first turns off the listenersocketAnd from theZygoteFile descriptor inherited from
    closeSocket(); .for(FileDescriptor fd: descriptors) { IoUtils.closeQuietly(fd); }...Copy the code
  • Next, depending on whether the startup parameter has--runtime-initAs well as--invoke-withHow do I initialize it
        if(parsedArgs.invokeWith ! =null) { WrapperInit.execApplication(......) ; . }else{...returnZygoteInit.zygoteInit(......) ; . }Copy the code
    • Start theapkThe application will have--runtime-initParameter, but--invoke-withUsually in the form ofnull
    • --invoke-withDon’t fornullWill be able to passexecThe way to startapp_processTo perform theJavaclass
    • Normally this is calledZygoteInit.zygoteInitmethods
  • ZygoteInit.zygoteInitThe method calls three more methods:RuntimeInit.commonInit(),ZygoteInit.nativeZygoteInit(),RuntimeInit.applicationInit()And the lastreturnaRunnableObject to the caller
    • commonInit()Simple initialization of some common configurations:
      • Set up theKillApplicationHandlerFor the defaultUncaughtExceptionHandler
      • Set the time zone
      • Set up thehttp.agentProperty forHttpURLConnection
      • resetAndroidtheLogsystem
      • throughNetworkManagementSocketTaggerSet up thesocketthetagIs used to collect traffic statistics
    • nativeZygoteInit()
      • This is a local method, but it’s important because it does one thing
      static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
      {
          gCurRuntime->onZygoteInit();
      }
      Copy the code
      • gCurRuntime? Does it look familiar? It isAppRuntimeAnd you can go up and look at itAndroidRuntimeThe initialization part
      • And then calledonZygoteInitLet’s seeAppRuntimeFunction implementation:
      virtual void onZygoteInit(a)
      {
          sp<ProcessState> proc = ProcessState::self();
          proc->startThreadPool();
      }
      Copy the code
      • This code does not also look familiar, ha ha ha, inBinderWe’ve already studied the chapters
      • Mainly initializationBinderSo that the application process can use itBinderthe
    • applicationInit()After the first two steps of initialization are complete, this function is executed and returned
      • Specifies the parameter for configuring the VMHeapUtilizationfor0.75 f
      • Set the currentSDKVersion
      • In addition to the above two, the most important is calledfindStaticMain()Function to findJavaOf the classmainMethod, and packaged intoRunnableIn the form of
      protected static Runnable findStaticMain(...) { Class<? > cl = Class.forName(className,true, classLoader);
          Method m = cl.getMethod("main".new Class[] { String[].class });
          int modifiers = m.getModifiers();
          return new MethodAndArgsCaller(m, argv);
      }
      static class MethodAndArgsCaller implements Runnable {
          private final Method mMethod;
          private finalString[] mArgs; .public void run(a) {... mMethod.invoke(null.newObject[] { mArgs }); . }}Copy the code
      • At this point, the function call ends
      • These are all someJava reflectionThe relevant knowledge, not clear words to learn oh

processOneCommandEnd point of a function

After the processOneCommand function has been executed, two things happen:

  • forThe child processReturn all packedMethodAndArgsCallerThe instance
  • forZygoteContinue the cycle

At this point, the child process’s runSelectLoop exits the loop and returns MethodAndArgsCaller, which eventually executes to:

    caller = zygoteServer.runSelectLoop(abiList);
    if(caller ! =null) {
        caller.run();
    }
Copy the code

Preloads system classes and resources

To speed up application startup, Android preloads common Java classes and some Framework resources into the Zygote process. These preloaded classes and resources are shared among all child processes forked by the Zygote process.

As shown in the figure:

The preload operation is done in the zygoteinit.main () function, via the preload() function, as follows:

    static void preload(TimingsTraceLog bootTimingsTraceLog) {...// Set soft reference protection to avoid GC collectionbeginIcuCachePinning(); .// Load the system classpreloadClasses(); .// Load system resourcespreloadResources(); .// nativePreloadAppProcessHALs(); .// Load OpenGL resources
        preloadOpenGL();
        // Load some shared so libraries (android, compiler_rt, jnigraphics)
        preloadSharedLibraries();
        // Load font resourcespreloadTextResources(); .// Load webView-related resources
        WebViewFactory.prepareWebViewInZygote();
        // Disable soft reference protection
        endIcuCachePinning();
        // Initialize JCA security-related parameters
        warmUpJcaProviders();
        Log.d(TAG, "end preload");
        sPreloadComplete = true;
    }
Copy the code

The comments are more detailed, and we’ll focus on loading Java classes, system resources, and shared libraries

Preload Java classes

Android puts all Java classes that need to be preloaded into text files called preload-classes.

In 9.0 source code files in the repository paths is in frameworks/base/config/preloaded – classes, content format is as follows:

#
# This file has been derived for mainline phone (and tablet) usage.
#
......
android.animation.Animator
android.app.ActivityThread
android.app.FragmentManager
......
Copy the code

The preloadClasses() function parses this file and loads all classes declared in it.

    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
    private static void preloadClasses(a) {... InputStream is =newFileInputStream(PRELOADED_CLASSES); . BufferedReader br =new BufferedReader(new InputStreamReader(is), 256);
        int count = 0;
        String line;
        while((line = br.readLine()) ! =null) {
            line = line.trim();
            if (line.startsWith("#") || line.equals("")) {
                continue; }... Class.forName(line,true.null); . }}Copy the code

At this point, the virtual machine is just finished loading these classes. The class is actually initialized only when it is first used actively

preloaded-classesWith all these classes declared, how is it generated?

The generation process is:

  • First of all, there’s oneframeworks/base/tools/preload/WritePreloadedClassFile.javaClass, which corresponds tomoduleispreload
  • And then there’s another oneframeworks/base/tools/preload/20100223.compiledfile
  • Then throughmmm frameworks/base/tools/preloadYou can compilepreload.jar
  • At last,java -Xss4M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiledAnd the result of that parsing is one that contains everythingJava classesThe contents of the list include:
    • The root object
    • Loaded by several processes
    • Average loading time
  • The last file generation path is in/frameworks/base/preloaded-classes

Compiled file compiled compiled is compiled without an compiled compiled file.

  • First of all, there’s oneframeworks/base/tools/preload/Compile.javaClass, also inpreloadIn, it is used for analysislogthepreloadThe information of
  • And then we can go throughjava -Xss512M -cp out/host/linux-x86/framework/preload.jar Compile logcat.txt 20201111.compiledTo generate the analysis, which is.compiledfile

Preloading system resources

The function for preloading system resources is preloadResources().

    private static void preloadResources(a) {... mResources = Resources.getSystem(); mResources.startPreloading();if (PRELOAD_RESOURCES) {
            ......
            TypedArray ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_drawables);
            intN = preloadDrawables(ar); ar.recycle(); . ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_color_state_lists); N = preloadColorStateLists(ar); ar.recycle(); . } mResources.finishPreloading(); . }Copy the code

The two functions for preloading the Framework’s resource core are:

  • preloadDrawablesLoading:drawableresources
  • preloadColorStateListsLoading:colorState definition resource

Behind a specific resource loading process to study it in detail, we first look at the definition of the preload resources, in the source directory frameworks/base/core/res/res/values/arrays. The XML:

    <array name="preloaded_drawables">
        <item>@drawable/action_bar_item_background_material</item>
        <item>@drawable/activated_background_material</item>
    </array>
    <array name="preloaded_color_state_lists">
        <item>@color/primary_text_dark</item>
        <item>@color/primary_text_dark_disable_only</item>
    </array>
Copy the code

Preload the shared library

The function for preloading shared libraries is preloadSharedLibraries() :

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

There are three SO files in total:

  • libandroid.so
  • libcompiler_rt.so
  • libjnigraphics.so

conclusion

Zygote process learning is over here, and further deepen the understanding of Android, ha ha ha ha!

Why is there a conclusion here at 叒叒叒叒? Ha ha ha ha, curious baby can look here!

A private project of no consequence

Knowledge is knowledge after all, skills still need to hone drops! Next article start learning Android resource management, come on ~~