0. Introduction
The underlying kernel of Android is built based on Linux, which is in the Native world, while the upper application of Android belongs to the Java world. So how did the system hatch the Java world from Native during the startup of the Android system? This is the main responsibility of Zygote, the protagonist of this article.
The Android system version selected in this article is 9.0 Pie. All code fragment paths in this article have been marked on the first line of the code block. The purpose of this article is to record your learning process and experience, and do not do business or profit. All big man blogs or works learned or quoted in the learning process will be marked as much as possible. Thank you for sharing. This paper draws lessons from the following:
- Android system startup -zygote chapter—— Yuan Huihui
- Source code analysis of Zygote startup process of Android system - Luo Shengyang
- [in depth understanding of Android Volume I full text - Chapter 4] in depth understanding of zygote -- Deng Pingfan
- Zygote process started by Android 10.0 system - [Android learning path] - IngresGe
1. Zygote in C/C + +
Last article Learning the init startup process of Android 9.0 (PIE) 1 process As mentioned in, init, the No. 1 process of the Android system, will start other processes through the. RC configuration file, and zygote is also started in this way. However, there are several zygote RC files in the pie\system\core\rootdir \ directory. Depending on the CPU configuration of the devices in the production environment, there are init.zygote32.rc and init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc these options. In the init.rc header, you can see that when importing the corresponding configured RC file, it is controlled according to the attribute ro.zygote. Because the attribute value of my production environment is zygote32, so reference resources Init process startup and init.rc full resolution of Android system and Detailed explanation of init.rc file of Android source code Take a look at init.zygote32.rc.
// pie\system\core\rootdir\init.zygote32.rc service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main //The class of this service is specified as main, which makes it easy to start or stop multiple services at the same time priority -20 //Refer to the process priority nice of linux. The value range is [- 20, 19]. The smaller the value, the higher the priority user root //Switch the user name to root before executing this service group root readproc reserved_disk //Similar to user, switch the group name socket zygote stream 660 root system //Create a socket of unix domain type in the directory / dev/socket / of the target machine. The socket file is named zygote and the port is 660. Running the program requires root or system permissions onrestart write /sys/android_power/request_state wake //When the service is restarted, write content to the specified file onrestart write /sys/power/state on onrestart restart audioserver //When the service is restarted, restart the specified service onrestart restart cameraserver onrestart restart media onrestart restart netd onrestart restart wificond writepid /dev/cpuset/foreground/tasks //When a child process is created, the pid of the process is written to the file
1.1,app_process
From the first line, we can see that zygote only renames the program, and actually runs the app under the target machine / system/bin directory_ Process program, followed by the parameters brought by running the program, that is, the app_process is a command-line program. First find the main function of the program. Since the function is long, let's divide the main code into several parts.
1.1.1. Android runtime initialization
// pie\frameworks\base\cmds\app_process\app_main.cpp int main(int argc, char* const argv[]) { if (!LOG_NDEBUG) { //Print the parameters transmitted in the debug mode to facilitate debugging and locating problems String8 argv_String; for (int i = 0; i < argc; ++i) { argv_String.append("\""); argv_String.append(argv[i]); argv_String.append("\" "); } ALOGV("app_process main with argv: %s", argv_String.string()); } AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); //Call the constructor of the parent class AndroidRuntime to initialize argc--; argv++; // Ignore the first parameter argv[0] occupied by the program name ............ }
AppRuntime here is inherited pie\frameworks\base\core\jni\AndroidRuntime.cpp. The constructor of AppRuntime class is empty, so go to call the constructor of parent class AndroidRuntime:
// pie\frameworks\base\core\jni\AndroidRuntime.cpp AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) : mExitWithoutCleanup(false), //Initialize the member variables of this class with the parameters passed in from the command line, mArgBlockStart(argBlockStart), //The starting position of the first parameter, mArgBlockLength(argBlockLength) //And the memory occupied by all parameter blocks { init_android_graphics(); //Initialize android graphics mOptions.setCapacity(20); //Some options are required as parameters when starting the virtual machine, and the number of options is set here assert(gCurRuntime == NULL); //Null pointer detection is required for each process gCurRuntime = this; }
1.1.2,spaced_commands TODO
This part is spaced_ The contents related to commands are a little confusing, but it does not affect future analysis, so put it first, and then add it when you understand it later. If there are leaders who know, you can also add it in the comments. Thank you very much.
// pie\frameworks\base\cmds\app_process\app_main.cpp int main(int argc, char* const argv[]) { ............ const char* spaced_commands[] = { "-cp", "-classpath" }; bool known_command = false; int i; for (i = 0; i < argc; i++) { if (known_command == true) { runtime.addOption(strdup(argv[i])); ALOGV("app_process main add known option '%s'", argv[i]); known_command = false; continue; } for (int j = 0; j < static_cast<int>(sizeof(spaced_commands) / sizeof(spaced_commands[0])); ++j) { if (strcmp(argv[i], spaced_commands[j]) == 0) { known_command = true; ALOGV("app_process main found known command '%s'", argv[i]); } } if (argv[i][0] != '-') { break; } if (argv[i][1] == '-' && argv[i][2] == 0) { ++i; // Skip --. break; } runtime.addOption(strdup(argv[i])); ALOGV("app_process main add option '%s'", argv[i]); } }
1.1.3 parameter analysis
// pie\frameworks\base\cmds\app_process\app_main.cpp int main(int argc, char* const argv[]) { ............ bool zygote = false; bool startSystemServer = false; bool application = false; String8 niceName; String8 className; ++i; // Skip unused parameter "/ system/bin" while (i < argc) { const char* arg = argv[i++]; if (strcmp(arg, "--zygote") == 0) { //When resolving to parameter "-- zygote", zygote = true; //The description is zygote mode niceName = ZYGOTE_NICE_NAME; //Prepare app_process alias ZYGOTE_NICE_NAME="zygote" } else if (strcmp(arg, "--start-system-server") == 0) { //When parsing to parameter "-- start system server" startSystemServer = true; //System needs to be started_ server } else if (strcmp(arg, "--application") == 0) { //If there is "-- application" in the parameter, application = true; //application mode } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName.setTo(arg + 12); //Set alias in application mode } else if (strncmp(arg, "--", 2) != 0) { className.setTo(arg); //Set class name in application mode break; } else { --i; break; } } Vector<String8> args; if (!className.isEmpty()) { //In non zygote mode, pass the parameter "application" to RuntimeInit args.add(application ? String8("application") : String8("tool")); runtime.setClassNameAndArgs(className, argc - i, argv + i); //Pass the class name and remaining parameters if (!LOG_NDEBUG) { String8 restOfArgs; char* const* argv_new = argv + i; int argc_new = argc - i; for (int k = 0; k < argc_new; ++k) { restOfArgs.append("\""); restOfArgs.append(argv_new[k]); restOfArgs.append("\" "); } ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string()); } } else { //In zygote mode maybeCreateDalvikCache(); //Create / data / Dalvik cache / directory if (startSystemServer) { args.add(String8("start-system-server")); //Pass the parameter "start system server" } char prop[PROP_VALUE_MAX]; if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) { //Read abi interface list LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.", ABI_LIST_PROPERTY); return 11; } String8 abiFlag("--abi-list="); abiFlag.append(prop); args.add(abiFlag); //Pass the CPU architecture type parameters supported by the system "-- ABI list = armeabi-v7a, armeabi,......" for (; i < argc; ++i) { args.add(String8(argv[i])); //Pass remaining parameters } } }
For the parameters passed in from the main function, analyze the relevant parameters in zygote mode and application mode, set the class name, process alias, set ID, pass parameters to the Android runtime and other actions according to the parameters.
1.1.4,runtime.start()
// pie\frameworks\base\cmds\app_process\app_main.cpp int main(int argc, char* const argv[]) { ............ if (!niceName.isEmpty()) { runtime.setArgv0(niceName.string(), true); //Set the alias of the process to the previously prepared niceName } if (zygote) { //zygote mode runtime.start("com.android.internal.os.ZygoteInit", args, zygote); //Call the start() function of the parent class AndroidRuntime } else if (className) { //application mode 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."); } }
This is mainly for app_ The process process sets the alias and then calls runtime.start() to call the start() function of the parent class AndroidRuntime, which we will look at later. The function calls at this location are shown in the following figure:
1.2,AndroidRuntime::start()
1.2.1 environmental variables
This function is still separated. The first part is relatively simple. It does some event printing, setting and detection related to environment variables.
// pie\frameworks\base\core\jni\AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ALOGD(">>>>>> START %s uid %d <<<<<<\n", className != NULL ? className : "(unknown)", getuid()); static const String8 startSystemServer("start-system-server"); bool primary_zygote = false; for (size_t i = 0; i < options.size(); ++i) { if (options[i] == startSystemServer) { primary_zygote = true; const int LOG_BOOT_PROGRESS_START = 3000; LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); //Print the startSystemServer event with time } } const char* rootDir = getenv("ANDROID_ROOT"); if (rootDir == NULL) { rootDir = "/system"; if (!hasDir("/system")) { LOG_FATAL("No root directory specified, and /system does not exist."); return; } setenv("ANDROID_ROOT", rootDir, 1); //Setting environment variables } const char* artRootDir = getenv("ANDROID_ART_ROOT"); //Check environment variables if (artRootDir == NULL) { LOG_FATAL("No ART directory specified with ANDROID_ART_ROOT environment variable."); return; } const char* i18nRootDir = getenv("ANDROID_I18N_ROOT"); if (i18nRootDir == NULL) { LOG_FATAL("No runtime directory specified with ANDROID_I18N_ROOT environment variable."); return; } const char* tzdataRootDir = getenv("ANDROID_TZDATA_ROOT"); if (tzdataRootDir == NULL) { LOG_FATAL("No tz data directory specified with ANDROID_TZDATA_ROOT environment variable."); return; } ............ }
1.2.2. Load virtual machine Library
// // pie\frameworks\base\core\jni\AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ JniInvocation jni_invocation; jni_invocation.Init(NULL); ............ }
Next, you can see the JniInvocation class. The related definitions of this class are located in the pie/libnativehelper / directory. Its main function is to provide external interfaces for dynamically calling the internal interfaces of the virtual machine. First, look at the Init() function invoked in the code:
// pie\libnativehelper\JniInvocation.cpp bool JniInvocation::Init(const char* library) { #ifdef __ANDROID__ char buffer[PROP_VALUE_MAX]; #else char* buffer = NULL; #endif library = GetLibrary(library, buffer); //Get the default library libart.so //RTLD_NOW: the open mode of the dlopen function is to immediately resolve all undefined symbols. If not, NULL will be returned in dlopen //RTLD_NODELETE: the library is not unloaded during dlclose() because even in JNI_ After deletejavavm returns, some threads may not have finished exiting. If the library is unloaded, this may lead to segment errors const int kDlopenFlags = RTLD_NOW | RTLD_NODELETE; handle_ = dlopen(library, kDlopenFlags); //Open the libart.so library and get the handle if (handle_ == NULL) { //If the opening fails, try again to ensure that libart.so is opened normally if (strcmp(library, kLibraryFallback) == 0) { ALOGE("Failed to dlopen %s: %s", library, dlerror()); return false; } ALOGW("Falling back from %s to %s after dlopen error: %s", library, kLibraryFallback, dlerror()); library = kLibraryFallback; handle_ = dlopen(library, kDlopenFlags); if (handle_ == NULL) { ALOGE("Failed to dlopen %s: %s", library, dlerror()); return false; } } //FindSymbol() function obtains JNI by calling dlsym() function_ GetDefaultJavaVMInitArgs,JNI_CreateJavaVM,JNI_GetCreatedJavaVMs addresses of these three functions if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_), "JNI_GetDefaultJavaVMInitArgs")) { return false; } if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_), "JNI_CreateJavaVM")) { return false; } if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_), "JNI_GetCreatedJavaVMs")) { return false; } return true; }
It can be seen that the main function of Init function is to find and open the default virtual machine library, and obtain the pointers of several key functions in the library according to the open file handle. It should be noted that the default library obtained by GetLibrary() here is the library of virtual machines. In earlier versions, such as 4.4 kitkak, the dalvik virtual machine is used, so the default library obtained is libdvm.so. In later versions, there will be art virtual machines, so the default library is libart.so. The main difference between the two is that the art virtual machine advances the bytecode translation optimization from runtime to installation, Exchange space for time to optimize the running and loading time.
1.2.3 start JavaVM
// pie\libnativehelper\JniInvocation.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ JNIEnv* env; if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) { return; } onVmCreated(env); //Empty interface ............ }
JNIEnv, which appears first, is a common thing in JNI programming. Its related definition is in the file pie\libnativehelper\include_jni\jni.h, so this is also a preparation for registering JNI functions later. Let's take a look at the startVm() function. Its main function is to create a Java VM. There are many addOption() operations in this function, which are intercepted here for description.
// pie\frameworks\base\core\jni\AndroidRuntime.cpp int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote) { JavaVMInitArgs initArgs; ............ addOption(***); //A lot of actions added to vector < javavmoption > moptions through attribute values or configuration are omitted here ............ //Assign a value to the member of the structure JavaVMInitArgs initArgs.version = JNI_VERSION_1_4; initArgs.options = mOptions.editArray(); initArgs.nOptions = mOptions.size(); initArgs.ignoreUnrecognized = JNI_FALSE; //Call the JNI_CreateJavaVM function in the libart.so library opened earlier to create a JavaVM. //JavaVM * is per process in essence, and JNIEnv * is per thread. If the creation here is successful, you can issue JNI calls. if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { ALOGE("JNI_CreateJavaVM failed\n"); return -1; } return 0; }
1.2.4 register JNI function
// pie\libnativehelper\JniInvocation.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ if (startReg(env) < 0) { //Register JNI functions ALOGE("Unable to register all android natives\n"); return; } ............ } // pie\libnativehelper\JniInvocation.cpp int AndroidRuntime::startReg(JNIEnv* env) { ATRACE_NAME("RegisterAndroidNatives"); //Set the function of creating threads in Threads.cpp to javaCreateThreadEtc androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); ALOGV("--- registering native functions ---\n"); //Each registration function will return one or more native referenced objects. At this time, the virtual machine has not been fully started, //Therefore, first use the Frame to manage the life cycle of local references. The parameter 200 is to store enough space. env->PushLocalFrame(200); if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { //JNI function registration action env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); return 0; }
reference resources android system core mechanism foundation (02) Thread class analysis , it can be seen that the androidSetCreateThreadFunc() function here is a function in pie\system\core\libutils\Threads.cpp. The action here is to set the thread creation function in the Threads class from androidCreateRawThreadEtc() to javaCreateThreadEtc() defined in AndroidRuntime.cpp. Although androidCreateRawThreadEtc() is finally called Function, but the thread function is changed to the javaThreadShell() function in androidruntime. CPP.
// pie\frameworks\base\core\jni\AndroidRuntime.cpp int AndroidRuntime::javaCreateThreadEtc( android_thread_func_t entryFunction, void* userData, const char* threadName, int32_t threadPriority, size_t threadStackSize, android_thread_id_t* threadId) { void** args = (void**) malloc(3 * sizeof(void*)); //Remember to use free in the javaThreadShell() function int result; LOG_ALWAYS_FATAL_IF(threadName == nullptr, "threadName not provided to javaCreateThreadEtc"); args[0] = (void*) entryFunction; args[1] = userData; args[2] = (void*) strdup(threadName); // Remember to use free in the javaThreadShell() function //Finally, androidCreateRawThreadEtc() is called, but the thread function is set to javaThreadShell() result = androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args, threadName, threadPriority, threadStackSize, threadId); return result; } // Thread function int AndroidRuntime::javaThreadShell(void* args) { void* start = ((void**)args)[0]; void* userData = ((void **)args)[1]; char* name = (char*) ((void **)args)[2]; free(args); //Release the args of malloc in the javaCreateThreadEtc function above JNIEnv* env; int result; if (javaAttachThread(name, &env) != JNI_OK) //Make the currently created thread visible to the VM return -1; result = (*(android_thread_func_t)start)(userData); //Run the created thread javaDetachThread(); //When a thread exits, the current thread is separated from the thread set visible to the virtual machine free(name); return result; }
After analyzing the androidSetCreateThreadFunc() function, let's look down at the more important register_jni_procs() function. Its main function is to register JNI functions:
// pie\frameworks\base\core\jni\AndroidRuntime.cpp static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) { //The RegJNIRec array passed in is full of function pointers of some registered functions, //Therefore, executing the member mProc of RegJNIRec is equivalent to executing the corresponding function to complete the registration action of each function. for (size_t i = 0; i < count; i++) { if (array[i].mProc(env) < 0) { #ifndef NDEBUG ALOGD("----------!!! %s failed to load\n", array[i].mName); #endif return -1; } } return 0; } // Definition of RegJNIRec #ifdef NDEBUG #define REG_JNI(name) { name } struct RegJNIRec { int (*mProc)(JNIEnv*); }; #else #define REG_JNI(name) { name, #name } struct RegJNIRec { int (*mProc)(JNIEnv*); const char* mName; }; #endif //Intercept the contents of some gRegJNI arrays static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_RuntimeInit), REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), REG_JNI(register_android_os_SystemClock), REG_JNI(register_android_util_CharsetUtils), ............ }
1.2.5 prepare the formal parameter argv of Java main function
You are ready to enter the Java World and call the Java main function, but you need to pass the class name "com.android.internal.os.ZygoteInit" of the main function to be executed and a bunch of option s related to the virtual machine settings added earlier. Therefore, you need to prepare and pass it in the form of String [].
// pie\frameworks\base\core\jni\AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ jclass stringClass; jobjectArray strArray; jstring classNameStr; stringClass = env->FindClass("java/lang/String"); //Find the Java String class first assert(stringClass != NULL); strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL); //Equivalent to strArray = new String[options.size() + 1]; assert(strArray != NULL); //The string needs to be converted to the UTF format of the Java world, which is equivalent to classNameStr = new String("com.android.internal.os.ZygoteInit"); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); env->SetObjectArrayElement(strArray, 0, classNameStr); //Equivalent to strArray[0] = classNameStr; i.e. "com.android.internal.os.ZygoteInit" //Convert all the options stored in the Vector to the String type of the Java World and store them in sequence in [1, options.size()] in strArray 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); } ............ }
1.2.6 start the virtual machine
// pie\frameworks\base\core\jni\AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ //Convert "com.android.internal.os.ZygoteInit" to "com/android/internal/os/ZygoteInit" format char* slashClassName = toSlashClassName(className != NULL ? className : ""); jclass startClass = env->FindClass(slashClassName); //Find the ZygoteInit class according to the path if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); //Find the static main method of the ZygoteInit class if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); //Execute the ZygoteInit.main() function #if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } ............ }
The action here is easy to understand. It is nothing more than finding com/android/internal/os/ZygoteInit.java, and then executing the main function of this class. Since then, it will enter the Java world. It should be noted that according to the comments in the code, this is the process of starting the virtual machine, and the current thread will become the main thread of the virtual machine, so this function can only exit when the virtual machine exits (such as crash) will return.
1.2.7 release resources when exiting
// pie\frameworks\base\core\jni\AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ............ free(slashClassName); //Free up space occupied by string ALOGD("Shutting down VM\n"); //Only when the virtual machine crashes or exits will it be executed here if (mJavaVM->DetachCurrentThread() != JNI_OK) //Detach the current thread from the thread set visible to the virtual machine ALOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) //Destroy the JavaVM you created earlier ALOGW("Warning: VM did not shut down cleanly\n"); }
At this point, the code of the C/C + + part of the Zygote process will come to an end, and then it will enter the Java world. There are also many contents in the Java part that need to be explained. In view of the poor impression of the previous long blog posts, this blog post will be written here first. The Java part of Zygote will be placed in the next blog post Zygote - the fertilized egg of the Java World in Android system (II) Welcome To Java). The old rules, the last figure summarizes the calling process: