Zygote进程介绍【转】

本文转载自:http://blog.csdn.net/yangwen123/article/details/17258023

Zygote进程介绍

 

Android系统中,存在不同的服务,这些服务可以分为:

Zygote进程介绍【转】

Android系统借用Binder通信机制实现了C/S架构设计,客户端应用程序如需要实现某些功能,只需请求指定的服务,由服务端来实现。Android服务包括以上的系统服务和应用服务,系统服务是指Android系统在启动过程就已经启动实现了的服务,对于系统服务又分为Java服务和本地服务,其实很好区分,Java服务是由Java代码编写而成,由SystemServer进程提供,而本地服务是由C/C++实现的服务,由Init进程在系统启动时启动的服务。应用服务是由开发者自行实现的某些特定服务。对于本地系统服务,我们知道它们是由Init进程来启动的,那对于Java系统服务,又是如何启动的呢?

Zygote进程介绍【转】

所有的应用程序进程以及系统服务进程SystemServer都是由Zygote进程孕育(fork)出来的,zygote和system_server分别是Java世界的半边天,任何一个进程的死亡都会导致Java崩溃。zygote本身是Native应用程序,与驱动内核无关。zygote进程对应的具体程序是“app_process”,这个可执行文件名称在Android.mk文件中指定,在Zygote进程启动时,将进程名称设置为"zygote"。
我们知道,Android系统是基于Linux内核的,而在Linux系统中,所有的进程都是init进程的子孙进程,也就是说,所有的进程都是直接或者间接地由init进程fork出来的。Zygote进程也不例外,它是在系统启动的过程,由init进程创建的。在系统启动脚本system/core/rootdir/init.rc文件中。

  1. //关键字service告诉init进程创建一个名为"zygote"的进程,这个zygote进程要执行的程序是/system/bin/app_process,后面是要传给app_process的参数。
  2. service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server   //socket关键字表示这个zygote进程需要一个名称为"zygote"的socket资源,这样,系统启动后,我们就可以在/dev/socket目录下看到有一个名为zygote的文件。这里定义的socket的类型为unix domain socket,它是用来作本地进程间通信用的
  3. socket zygote stream 666
  4. critical
  5. onrestart write /sys/android_power/request_state wake
  6. onrestart write /sys/power/state on
  7. onrestart restart media
  8. onrestart restart netd
  9. onrestart restart mlistener
-Xzygote:该参数将作为虚拟机启动时所需的参数;
/system/bin:代表虚拟机程序所在目录;
--zygote:指明以ZygoteInit.java类中的main函数作为虚拟机执行入口;
--start-system-server:告诉Zygote进程启动SystemServer进程;
 

Zygote进程执行代码:
frameworks\base\cmds\app_process\App_main.cpp
frameworks\base\core\jni\AndroidRuntime.cpp
frameworks\base\core\java\com\android\internal\os\ZygoteInit.java

 
Zygote启动分为两个阶段:
1. 虚拟机启动 --- 通过native启动
startVm(&mJavaVM, &env) 启动虚拟机
onVmCreated(env)         虚拟机启动后的初始化
startReg(env)             注册JNI函数
env->CallStaticVoidMethod(startClass, startMeth, strArray) 调用ZygoteInit类的main函数开创java世界 
2.SystemServer进程 --- 通过Java启动
registerZygoteSocket() 为zygote进程注册监听socket
preload()            加载常用的JAVA类和系统资源
startSystemServer()    启动SystemServer进程
runSelectLoopMode()  进入循环监听模式
closeServerSocket()    进程退出时,关闭socket监听

Zygote进程介绍【转】

Zygote进程包含两个主要模块:

1. Socket服务端,该Socket服务端用于接收启动新的Dalvik进程命令。

2. Framework共享类及共享资源,当Zygote进程启动后,会装载一些共享类和资源,共享类是在preload-classes文件中定义的,共享资源是在preload-resources文件中定义。因为其他Dalvik进程是由Zygote进程孵化出来的,因此只要Zygote装载好了这些类和资源后,新的Dalvik进程就不需要在装载这些类和资源了,它们共享Zygote进程的资源和类。

app_process

 

App_main.cpp

Zygote进程是通过app_process启动的,app_process通过解析命令行参数,然后启动一个Android虚拟机,调用Java的入口函数从而启动一个进程,app_process也可以启动其他进程,比如monkey,am,pm等。

  1. int main(int argc, const char* const argv[])
  2. {
  3. //zygote 是由init进程fork而来,init.rc文件中为zygote进程设置的启动参数如下
  4. //argc = 4
  5. //argv = [-Xzygote, /system/bin, --zygote, --start-system-server]
  6. // These are global variables in ProcessState.cpp
  7. mArgC = argc;
  8. mArgV = argv;
  9. mArgLen = 0;
  10. for (int i=0; i<argc; i++) {
  11. mArgLen += strlen(argv[i]) + 1;
  12. }
  13. mArgLen--;
  14. AppRuntime runtime;
  15. const char* argv0 = argv[0];
  16. // ignore argv[0]
  17. argc--;
  18. argv++;
  19. // Everything up to '--' or first non '-' arg goes to the vm
  20. //添加虚拟机参数选项
  21. //addVmArguments(3, [/system/bin, --zygote, --start-system-server])结果返回 i=0
  22. int i = runtime.addVmArguments(argc, argv);
  23. bool zygote = false;
  24. bool startSystemServer = false;
  25. bool application = false;
  26. const char* parentDir = NULL;
  27. const char* niceName = NULL;
  28. const char* className = NULL;
  29. while (i < argc) {
  30. const char* arg = argv[i++];
  31. if (!parentDir) {//第一个参数为parentDir
  32. parentDir = arg;
  33. //表示启动zygote进程
  34. } else if (strcmp(arg, "--zygote") == 0) {
  35. zygote = true;
  36. niceName = "zygote";
  37. //表示启动SystemServer进程
  38. } else if (strcmp(arg, "--start-system-server") == 0) {
  39. startSystemServer = true;
  40. //表示启动应用程序进程
  41. } else if (strcmp(arg, "--application") == 0) {
  42. application = true;
  43. //进程名称
  44. } else if (strncmp(arg, "--nice-name=", 12) == 0) {
  45. niceName = arg + 12;
  46. } else {
  47. //剩余参数为启动类名称
  48. className = arg;
  49. break;
  50. }
  51. }
  52. if (niceName && *niceName) {
  53. setArgv0(argv0, niceName);
  54. set_process_name(niceName);//修改进程名称
  55. }
  56. // 设置虚拟机运行目录
  57. runtime.mParentDir = parentDir;
  58. //如果启动的是zygote进程
  59. if (zygote) {
  60. // do last shutdown check
  61. ALOGV("doLastShutDownCheck");
  62. doLastShutDownCheck();
  63. //调用Runtimed的start函数启动SystemServer进程,startSystemServer = true
  64. runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
  65. //如果启动类名存在,
  66. //在Android中,monkey,am,pm这些命令是一个脚本文件,脚本的内容就是使用app_process启动一个进程
  67. //对于am:
  68. //export CLASSPATH=$base/framework/am.jar
  69. //exec app_process $base/bin com.android.commands.am.Am "$@"
  70. //argc = 2
  71. //argv = [$base/bin, com.android.commands.am.Am]
  72. } else if (className) {
  73. // Remainder of args get passed to startup class main()
  74. runtime.mClassName = className;
  75. runtime.mArgC = argc - i;
  76. runtime.mArgV = argv + i;
  77. runtime.start("com.android.internal.os.RuntimeInit",application ? "application" : "tool");
  78. } else {
  79. fprintf(stderr, "Error: no class name or --zygote supplied.\n");
  80. app_usage();
  81. LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
  82. return 10;
  83. }
  84. }

这个函数的主要作用就是创建一个AppRuntime变量,然后调用它的start成员函数。AppRuntime类的声明和实现在App_main.cpp中,派生于AndroidRuntime类

Zygote进程介绍【转】

从上面的代码可以知道,app_process就是通过启动一个Android虚拟机并加载相应的Java类来启动一个进程。

虚拟机启动过程

 
AndroidRuntime.cpp
  1. void AndroidRuntime::start(const char* className, const char* options)
  2. {
  3. blockSigpipe();
  4. /*
  5. * 'startSystemServer == true' means runtime is obsolete and not run from
  6. * init.rc anymore, so we print out the boot start event here.
  7. */
  8. if (strcmp(options, "start-system-server") == 0) {
  9. /* track our progress through the boot sequence */
  10. const int LOG_BOOT_PROGRESS_START = 3000;
  11. LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
  12. }
  13. //设置ANDROID_ROOT环境变量
  14. const char* rootDir = getenv("ANDROID_ROOT");
  15. if (rootDir == NULL) {
  16. rootDir = "/system";//如果获取结果为Null,则设置为"/system"
  17. if (!hasDir("/system")) {
  18. LOG_FATAL("No root directory specified, and /android does not exist.");
  19. return;
  20. }
  21. setenv("ANDROID_ROOT", rootDir, 1);//重新设置环境变量ANDROID_ROOT
  22. }
  23. //启动虚拟机
  24. JNIEnv* env;
  25. if (startVm(&mJavaVM, &env) != 0) {
  26. return;
  27. }
  28. onVmCreated(env);
  29. //注册JNI函数
  30. if (startReg(env) < 0) {
  31. ALOGE("Unable to register all android natives\n");
  32. return;
  33. }
  34. //这里调用java类的main入口函数
  35. jclass stringClass;
  36. jobjectArray strArray;
  37. jstring classNameStr;
  38. jstring optionsStr;
  39. //通过JNI查找java的java.lang.String类
  40. stringClass = env->FindClass("java/lang/String");
  41. assert(stringClass != NULL);
  42. //创建字符串数组String strArray[] = new String[2];
  43. strArray = env->NewObjectArray(2, stringClass, NULL);
  44. assert(strArray != NULL);
  45. //创建字符串classNameStr
  46. //对于zygote进程:  classNameStr = new String("com.android.internal.os.ZygoteInit")
  47. //对于启动应用进程:classNameStr = new String("com.android.internal.os.RuntimeInit")
  48. classNameStr = env->NewStringUTF(className);
  49. assert(classNameStr != NULL);
  50. //设置字符串数组元素strArray[0]
  51. env->SetObjectArrayElement(strArray, 0, classNameStr);
  52. //创建字符串optionsStr,对应进程启动参数
  53. optionsStr = env->NewStringUTF(options);
  54. //设置字符串数组元素strArray[1]
  55. env->SetObjectArrayElement(strArray, 1, optionsStr);
  56. //为符合JNI规范,将com.android.xxx中的.换成/,变为slashClassName = com/android/xxx
  57. char* slashClassName = toSlashClassName(className);
  58. //查找Java类com/android/xxx
  59. jclass startClass = env->FindClass(slashClassName);
  60. if (startClass == NULL) {
  61. ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
  62. } else {
  63. //找到ZygoteInit类的静态main方法的jMethodID
  64. jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
  65. "([Ljava/lang/String;)V");
  66. if (startMeth == NULL) {
  67. ALOGE("JavaVM unable to find main() in '%s'\n", className);
  68. //在调用Java类的main方法后,zygote就进入了java世界
  69. } else {
  70. env->CallStaticVoidMethod(startClass, startMeth, strArray);
  71. }
  72. }
  73. free(slashClassName);
  74. ALOGD("Shutting down VM\n");
  75. if (mJavaVM->DetachCurrentThread() != JNI_OK)
  76. ALOGW("Warning: unable to detach main thread\n");
  77. if (mJavaVM->DestroyJavaVM() != 0)
  78. ALOGW("Warning: VM did not shut down cleanly\n");
  79. }

AndroidRuntime::start()中完成四个任务:
① startVm(&mJavaVM, &env) 启动虚拟机
② onVmCreated(env)        虚拟机启动后的初始化
③ startReg(env)           注册JNI函数
④ env->CallStaticVoidMethod(startClass, startMeth, strArray) 调用ZygoteInit类的main函数开创java世界

启动虚拟机

 
  1. int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
  2. {
  3. int result = -1;
  4. JavaVMInitArgs initArgs;
  5. JavaVMOption opt;
  6. char propBuf[PROPERTY_VALUE_MAX];
  7. char stackTraceFileBuf[PROPERTY_VALUE_MAX];
  8. char dexoptFlagsBuf[PROPERTY_VALUE_MAX];
  9. char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX];
  10. char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];
  11. char heapstartsizeOptsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
  12. char heapsizeOptsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
  13. char heapgrowthlimitOptsBuf[sizeof("-XX:HeapGrowthLimit=")-1 + PROPERTY_VALUE_MAX];
  14. char extraOptsBuf[PROPERTY_VALUE_MAX];
  15. char* stackTraceFile = NULL;
  16. bool checkJni = false;
  17. bool checkDexSum = false;
  18. bool logStdio = false;
  19. enum {
  20. kEMDefault,
  21. kEMIntPortable,
  22. kEMIntFast,
  23. #if defined(WITH_JIT)
  24. kEMJitCompiler,
  25. #endif
  26. } executionMode = kEMDefault;
  27. property_get("dalvik.vm.checkjni", propBuf, "");
  28. if (strcmp(propBuf, "true") == 0) {
  29. checkJni = true;
  30. } else if (strcmp(propBuf, "false") != 0) {
  31. /* property is neither true nor false; fall back on kernel parameter */
  32. property_get("ro.kernel.android.checkjni", propBuf, "");
  33. if (propBuf[0] == '1') {
  34. checkJni = true;
  35. }
  36. }
  37. property_get("dalvik.vm.execution-mode", propBuf, "");
  38. if (strcmp(propBuf, "int:portable") == 0) {
  39. executionMode = kEMIntPortable;
  40. } else if (strcmp(propBuf, "int:fast") == 0) {
  41. executionMode = kEMIntFast;
  42. #if defined(WITH_JIT)
  43. } else if (strcmp(propBuf, "int:jit") == 0) {
  44. executionMode = kEMJitCompiler;
  45. #endif
  46. }
  47. property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, "");
  48. property_get("dalvik.vm.check-dex-sum", propBuf, "");
  49. if (strcmp(propBuf, "true") == 0) {
  50. checkDexSum = true;
  51. }
  52. property_get("log.redirect-stdio", propBuf, "");
  53. if (strcmp(propBuf, "true") == 0) {
  54. logStdio = true;
  55. }
  56. strcpy(enableAssertBuf, "-ea:");
  57. property_get("dalvik.vm.enableassertions", enableAssertBuf+4, "");
  58. strcpy(jniOptsBuf, "-Xjniopts:");
  59. property_get("dalvik.vm.jniopts", jniOptsBuf+10, "");
  60. /* route exit() to our handler */
  61. opt.extraInfo = (void*) runtime_exit;
  62. opt.optionString = "exit";
  63. mOptions.add(opt);
  64. /* route fprintf() to our handler */
  65. opt.extraInfo = (void*) runtime_vfprintf;
  66. opt.optionString = "vfprintf";
  67. mOptions.add(opt);
  68. /* register the framework-specific "is sensitive thread" hook */
  69. opt.extraInfo = (void*) runtime_isSensitiveThread;
  70. opt.optionString = "sensitiveThread";
  71. mOptions.add(opt);
  72. opt.extraInfo = NULL;
  73. /* enable verbose; standard options are { jni, gc, class } */
  74. //options[curOpt++].optionString = "-verbose:jni";
  75. opt.optionString = "-verbose:gc";
  76. mOptions.add(opt);
  77. //options[curOpt++].optionString = "-verbose:class";
  78. /*
  79. * The default starting and maximum size of the heap.  Larger
  80. * values should be specified in a product property override.
  81. */
  82. strcpy(heapstartsizeOptsBuf, "-Xms");
  83. property_get("dalvik.vm.heapstartsize", heapstartsizeOptsBuf+4, "4m");
  84. opt.optionString = heapstartsizeOptsBuf;
  85. mOptions.add(opt);
  86. strcpy(heapsizeOptsBuf, "-Xmx");
  87. property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
  88. opt.optionString = heapsizeOptsBuf;
  89. mOptions.add(opt);
  90. // Increase the main thread's interpreter stack size for bug 6315322.
  91. opt.optionString = "-XX:mainThreadStackSize=24K";
  92. mOptions.add(opt);
  93. strcpy(heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
  94. property_get("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf+20, "");
  95. if (heapgrowthlimitOptsBuf[20] != '\0') {
  96. opt.optionString = heapgrowthlimitOptsBuf;
  97. mOptions.add(opt);
  98. }
  99. /*
  100. * Enable or disable dexopt features, such as bytecode verification and
  101. * calculation of register maps for precise GC.
  102. */
  103. property_get("dalvik.vm.dexopt-flags", dexoptFlagsBuf, "");
  104. if (dexoptFlagsBuf[0] != '\0') {
  105. const char* opc;
  106. const char* val;
  107. opc = strstr(dexoptFlagsBuf, "v=");     /* verification */
  108. if (opc != NULL) {
  109. switch (*(opc+2)) {
  110. case 'n':   val = "-Xverify:none";      break;
  111. case 'r':   val = "-Xverify:remote";    break;
  112. case 'a':   val = "-Xverify:all";       break;
  113. default:    val = NULL;                 break;
  114. }
  115. if (val != NULL) {
  116. opt.optionString = val;
  117. mOptions.add(opt);
  118. }
  119. }
  120. opc = strstr(dexoptFlagsBuf, "o=");     /* optimization */
  121. if (opc != NULL) {
  122. switch (*(opc+2)) {
  123. case 'n':   val = "-Xdexopt:none";      break;
  124. case 'v':   val = "-Xdexopt:verified";  break;
  125. case 'a':   val = "-Xdexopt:all";       break;
  126. case 'f':   val = "-Xdexopt:full";      break;
  127. default:    val = NULL;                 break;
  128. }
  129. if (val != NULL) {
  130. opt.optionString = val;
  131. mOptions.add(opt);
  132. }
  133. }
  134. ...
  135. }
  136. }

初始化虚拟机

 
  1. virtual void onVmCreated(JNIEnv* env)
  2. {
  3. //在启动zygote时,没有设置mClassName,
  4. if (mClassName == NULL) {
  5. return; // Zygote. Nothing to do here.
  6. }
  7. //在启动其他进程时,设置了Java启动类
  8. //将com.android.xxx中的.换成/,变为slashClassName = com/android/xxx
  9. char* slashClassName = toSlashClassName(mClassName);
  10. //查找Java类com/android/xxx,mClass为进程启动类
  11. mClass = env->FindClass(slashClassName);
  12. if (mClass == NULL) {
  13. ALOGE("ERROR: could not find class '%s'\n", mClassName);
  14. }
  15. free(slashClassName);
  16. //创建java类的全局引用,保存到mclass中
  17. mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
  18. }

注册JNI函数

 

创建好了虚拟机,因此需要给该虚拟机注册一些JNI函数。

  1. int AndroidRuntime::startReg(JNIEnv* env)
  2. {
  3. /*
  4. * This hook causes all future threads created in this process to be
  5. * attached to the JavaVM.  (This needs to go away in favor of JNI
  6. * Attach calls.) 设置线程创建函数为javaCreateThreadEtc
  7. */
  8. androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
  9. LOGV("--- registering native functions ---\n");
  10. /*
  11. * Every "register" function calls one or more things that return
  12. * a local reference (e.g. FindClass).  Because we haven't really
  13. * started the VM yet, they're all getting stored in the base frame
  14. * and never released.  Use Push/Pop to manage the storage.
  15. */
  16. env->PushLocalFrame(200);
  17. //注册JNI函数,所有的JNI函数存放在gRegJNI全局数组中
  18. if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
  19. env->PopLocalFrame(NULL);
  20. return -1;
  21. }
  22. env->PopLocalFrame(NULL);
  23. //createJavaThread("fubar", quickTest, (void*) "hello");
  24. return 0;
  25. }

JNI函数注册过程,循环调用gRegJNI数组中的每个元素的mProc方法

  1. //JNI函数注册方法
  2. static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
  3. {
  4. for (size_t i = 0; i < count; i++) {
  5. if (array[i].mProc(env) < 0) {
  6. #ifndef NDEBUG
  7. LOGD("----------!!! %s failed to load\n", array[i].mName);
  8. #endif
  9. return -1;
  10. }
  11. }
  12. return 0;
  13. }

使用宏REG_JNI将系统中所有JNI注册函数添加到JNI数组gRegJNI中。

  1. //gRegJNI全局数组中
  2. static const RegJNIRec gRegJNI[] = {
  3. REG_JNI(register_android_debug_JNITest),
  4. REG_JNI(register_com_android_internal_os_RuntimeInit),
  5. REG_JNI(register_android_os_SystemClock),
  6. REG_JNI(register_android_util_EventLog),
  7. REG_JNI(register_android_util_Log),
  8. REG_JNI(register_android_util_FloatMath),
  9. REG_JNI(register_android_text_format_Time),
  10. REG_JNI(register_android_pim_EventRecurrence),
  11. REG_JNI(register_android_content_AssetManager),
  12. REG_JNI(register_android_content_StringBlock),
  13. REG_JNI(register_android_content_XmlBlock),
  14. REG_JNI(register_android_emoji_EmojiFactory),
  15. REG_JNI(register_android_security_Md5MessageDigest),
  16. REG_JNI(register_android_text_AndroidCharacter),
  17. REG_JNI(register_android_text_AndroidBidi),
  18. REG_JNI(register_android_text_KeyCharacterMap),
  19. REG_JNI(register_android_os_Process),
  20. REG_JNI(register_android_os_Binder),
  21. REG_JNI(register_android_view_Display),
  22. REG_JNI(register_android_nio_utils),
  23. REG_JNI(register_android_graphics_PixelFormat),
  24. REG_JNI(register_android_graphics_Graphics),
  25. REG_JNI(register_android_view_Surface),
  26. REG_JNI(register_android_view_ViewRoot),
  27. REG_JNI(register_com_google_android_gles_jni_EGLImpl),
  28. REG_JNI(register_com_google_android_gles_jni_GLImpl),
  29. REG_JNI(register_android_opengl_jni_GLES10),
  30. REG_JNI(register_android_opengl_jni_GLES10Ext),
  31. REG_JNI(register_android_opengl_jni_GLES11),
  32. REG_JNI(register_android_opengl_jni_GLES11Ext),
  33. REG_JNI(register_android_opengl_jni_GLES20),
  34. ....
  35. };

调用Java类的入口函数

AndroidRuntime通过JNI方式调用Java类的入口main函数,从此开辟了Java世界。在这里通过传递不同的启动类,就可以实现通过app_process启动不同的进程,前面分析app_process的main函数时,已经知道对于zygote进程来说,传递的启动类为com.android.internal.os.ZygoteInit,而如果是启动其他进程则传递的是com.android.internal.os.RuntimeInit。

Zygote进程启动

 

从C++层调用Java层的ZygoteInit类的main函数,从此开辟了Java世界。

  1. public static void main(String argv[]) {
  2. //传入的参数argv = ["com.android.internal.os.ZygoteInit","true"]
  3. try {
  4. //设置虚拟机的最小堆栈大小
  5. VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024);
  6. // Start profiling the zygote initialization.启动性能统计
  7. SamplingProfilerIntegration.start();
  8. //注册zygote等待客户端连接的socket
  9. registerZygoteSocket();
  10. EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());
  11. //预加载java类和资源
  12. preloadClasses();
  13. preloadResources();
  14. EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());
  15. // Finish profiling the zygote initialization.结束统计并生成结果文件
  16. SamplingProfilerIntegration.writeZygoteSnapshot();
  17. // Do an initial gc to clean up after startup,执行垃圾回收
  18. gc();
  19. // If requested, start system server directly from Zygote
  20. if (argv.length != 2) {
  21. throw new RuntimeException(argv[0] + USAGE_STRING);
  22. }
  23. if (argv[1].equals("true")) {
  24. //启动SystemServer进程
  25. startSystemServer();
  26. } else if (!argv[1].equals("false")) {
  27. throw new RuntimeException(argv[0] + USAGE_STRING);
  28. }
  29. Log.i(TAG, "Accepting command socket connections");
  30. //boolean ZYGOTE_FORK_MODE = false; 因此调用runSelectLoopMode()函数
  31. if (ZYGOTE_FORK_MODE) {
  32. runForkMode();
  33. } else {
  34. runSelectLoopMode();
  35. }
  36. closeServerSocket(); //关闭socket
  37. } catch (MethodAndArgsCaller caller) {
  38. //捕获SytemServer进程调用RuntimeInit.java 中zygoteInit函数抛出的MethodAndArgsCaller异常
  39. caller.run();
  40. } catch (RuntimeException ex) {
  41. Log.e(TAG, "Zygote died with exception", ex);
  42. closeServerSocket();
  43. throw ex;
  44. }
  45. }

在以上ZygoteInit类的main中完成以下五个工作:
① registerZygoteSocket() 为zygote进程注册监听socket
② preload()            加载常用的JAVA类和系统资源
③ startSystemServer()    启动SystemServer进程
④ runSelectLoopMode()  进入循环监听模式
⑤ closeServerSocket()    进程退出时,关闭socket监听

启动Socket服务端口

 

zygote 并没有采用binder通信机制,而是采用基于AF_UNIX类型的socket通信方式

  1. private static void registerZygoteSocket() {
  2. if (sServerSocket == null) {
  3. int fileDesc;
  4. try {
  5. //从环境变量中获取文件句柄,这个socket接口是通过文件描述符来创建的,这个文件描符代表的就是我们前面说的/dev/socket/zygote文件了。这个文件描述符是通过环境变量ANDROID_SOCKET_ENV得到的,关于socket的创建及环境变量的设置请参考init进程源码分析
  6. String env = System.getenv(ANDROID_SOCKET_ENV);
  7. fileDesc = Integer.parseInt(env);
  8. } catch (RuntimeException ex) {
  9. throw new RuntimeException(ANDROID_SOCKET_ENV + " unset or invalid", ex);
  10. }
  11. try {
  12. //创建服务端socket,该socket将监听并接受客户端请求
  13. sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));
  14. } catch (IOException ex) {
  15. throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);
  16. }
  17. }
  18. }

函数首先调用System.getenv()获取系统为Zygote进程分配的Socket文件描述符,然后调用createFileDescriptor(fileDesc)创建一个真正的Socket文件描述符。Socket的使用方式有:

1. 阻塞方式:使用listen()监听某个端口,然后调用read()函数从这个端口读取数据,当Socket端口没有数据时,read()函数将一直等待,直到读取到数据才返回;

2. 非阻塞方式:使用Linux系统调用select()函数监测Socket文件描述符,当该文件描述符上有数据时,自动触发中断,在中断处理函数中去读取文件描述符上的数据,LocalServerSocket就是对非阻塞式Socket的封装;

预加载类和资源

 
  1. private static void preloadClasses() {
  2. final VMRuntime runtime = VMRuntime.getRuntime();
  3. //通过反射机制获取输入流,类资源文件为"preloaded-classes"
  4. InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream(PRELOADED_CLASSES);
  5. if (is == null) {
  6. Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
  7. } else {
  8. Log.i(TAG, "Preloading classes...");
  9. long startTime = SystemClock.uptimeMillis();
  10. // Drop root perms while running static initializers.
  11. //设置有效组ID和有效用户ID
  12. setEffectiveGroup(UNPRIVILEGED_GID);
  13. setEffectiveUser(UNPRIVILEGED_UID);
  14. // Alter the target heap utilization.  With explicit GCs this
  15. // is not likely to have any effect.
  16. float defaultUtilization = runtime.getTargetHeapUtilization();
  17. runtime.setTargetHeapUtilization(0.8f);
  18. // Start with a clean slate.
  19. runtime.gcSoftReferences();
  20. runtime.runFinalizationSync();
  21. Debug.startAllocCounting();
  22. try {
  23. BufferedReader br= new BufferedReader(new InputStreamReader(is), 256);
  24. int count = 0;
  25. String line;
  26. //一行一行读取文件内容
  27. while ((line = br.readLine()) != null) {
  28. // Skip comments and blank lines.
  29. line = line.trim();
  30. if (line.startsWith("#") || line.equals("")) {
  31. continue;
  32. }
  33. try {
  34. if (Config.LOGV) {
  35. Log.v(TAG, "Preloading " + line + "...");
  36. }
  37. //通过Java反射机制加载类,每一行储存的是类名
  38. Class.forName(line);
  39. if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
  40. if (Config.LOGV) {
  41. Log.v(TAG," GC at " + Debug.getGlobalAllocSize());
  42. }
  43. runtime.gcSoftReferences();
  44. runtime.runFinalizationSync();
  45. Debug.resetGlobalAllocSize();
  46. }
  47. count++;
  48. } catch (ClassNotFoundException e) {
  49. Log.w(TAG, "Class not found for preloading: " + line);
  50. } catch (Throwable t) {
  51. Log.e(TAG, "Error preloading " + line + ".", t);
  52. if (t instanceof Error) {
  53. throw (Error) t;
  54. }
  55. if (t instanceof RuntimeException) {
  56. throw (RuntimeException) t;
  57. }
  58. throw new RuntimeException(t);
  59. }
  60. }
  61. Log.i(TAG, "...preloaded " + count + " classes in "
  62. + (SystemClock.uptimeMillis()-startTime) + "ms.");
  63. } catch (IOException e) {
  64. Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
  65. } finally {
  66. // Restore default.
  67. runtime.setTargetHeapUtilization(defaultUtilization);
  68. Debug.stopAllocCounting();
  69. // Bring back root. We'll need it later.
  70. setEffectiveUser(ROOT_UID);
  71. setEffectiveGroup(ROOT_GID);
  72. }
  73. }
  74. }

预装载的类列表保存在framework.jar中的名为preloaded-classes的文本文件中,该文件是通过framework/base/tools/prload/WritePreloadedClassFile.java类生成的,产生preloaded-classes文件的方法是在Android源码根目录执行以下命令:

  1. java -Xss512M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled
  2. 1517 classses were loaded by more than one app.
  3. Added 147 more to speed up applications.
  4. 1664 total classes will be preloaded.
  5. Writing object model...
  6. Done!

最后生成frameworks/base/preloaded-classes文本文件。preloadClasses函数就是读取preloaded-classes文件,该文件中的每一行代表一个具体的类,然后通过Class.forName()装载这些类,preloadClasses 执行时间比较长,也是导致android系统启动慢的原因。

加载共享资源

 
  1. private static void preloadResources() {
  2. final VMRuntime runtime = VMRuntime.getRuntime();
  3. Debug.startAllocCounting();
  4. try {
  5. if(LESS_GC) {
  6. System.gc();
  7. runtime.runFinalizationSync();
  8. }
  9. mResources = Resources.getSystem();
  10. mResources.startPreloading();
  11. if (PRELOAD_RESOURCES) {
  12. Log.i(TAG, "Preloading resources...");
  13. long startTime = SystemClock.uptimeMillis();
  14. //获取frameworks/base/core/res/res/values/arrays.xml中定义的数组preloaded_drawables
  15. TypedArray ar = mResources.obtainTypedArray(
  16. com.android.internal.R.array.preloaded_drawables);
  17. //加载drawable资源
  18. int N = preloadDrawables(runtime, ar);
  19. ar.recycle();
  20. Log.i(TAG, "...preloaded " + N + " resources in "
  21. + (SystemClock.uptimeMillis()-startTime) + "ms.");
  22. startTime = SystemClock.uptimeMillis();
  23. //获取frameworks/base/core/res/res/values/arrays.xml中定义的数组preloaded_color_state_lists
  24. ar = mResources.obtainTypedArray(
  25. com.android.internal.R.array.preloaded_color_state_lists);
  26. //加载color资源
  27. N = preloadColorStateLists(runtime, ar);
  28. ar.recycle();
  29. Log.i(TAG, "...preloaded " + N + " resources in "
  30. + (SystemClock.uptimeMillis()-startTime) + "ms.");
  31. }
  32. mResources.finishPreloading();
  33. } catch (RuntimeException e) {
  34. Log.w(TAG, "Failure preloading resources", e);
  35. } finally {
  36. Debug.stopAllocCounting();
  37. }
  38. }

preload-resources实在frameworks/base/core/res/res/values/arrays.xml中定义的,包括drawable资源和color资源。函数调用preloadDrawables()来加载drawable资源,drawable资源定义:

  1. <array name="preloaded_drawables">
  2. <item>@drawable/toast_frame_holo</item>
  3. <item>@drawable/btn_check_on_pressed_holo_light</item>
  4. <item>@drawable/btn_check_on_holo_light</item>
  5. ....
  6. </array>
 
  1. private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
  2. int N = ar.length();
  3. for (int i=0; i<N; i++) {
  4. if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
  5. if (false) {
  6. Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
  7. }
  8. if(LESS_GC) {
  9. System.gc();
  10. runtime.runFinalizationSync();
  11. Debug.resetGlobalAllocSize();
  12. }
  13. }
  14. int id = ar.getResourceId(i, 0);
  15. if (false) {
  16. Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
  17. }
  18. if (id != 0) {
  19. //将资源加载到mResources全局变量中
  20. Drawable dr = mResources.getDrawable(id);
  21. if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
  22. Log.w(TAG, "Preloaded drawable resource #0x"
  23. + Integer.toHexString(id)
  24. + " (" + ar.getString(i) + ") that varies with configuration!!");
  25. }
  26. }
  27. }
  28. return N;
  29. }
接着调用preloadColorStateLists()来加载color资源。这些资源被保存到全局变量mResources中。
  1. <array name="preloaded_color_state_lists">
  2. <item>@color/primary_text_dark</item>
  3. <item>@color/primary_text_dark_disable_only</item>
  4. <item>@color/primary_text_dark_nodisable</item>
  5. ....
  6. </array>
 
  1. private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
  2. int N = ar.length();
  3. for (int i=0; i<N; i++) {
  4. if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
  5. if (false) {
  6. Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
  7. }
  8. if(LESS_GC) {
  9. System.gc();
  10. runtime.runFinalizationSync();
  11. Debug.resetGlobalAllocSize();
  12. }
  13. }
  14. int id = ar.getResourceId(i, 0);
  15. if (false) {
  16. Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
  17. }
  18. if (id != 0) {
  19. //将资源加载到mResources全局变量中
  20. mResources.getColorStateList(id);
  21. }
  22. }
  23. return N;
  24. }

启动SystemServer进程

 
  1. private static boolean startSystemServer()
  2. throws MethodAndArgsCaller, RuntimeException {
  3. /* Hardcoded command line to start the system server */
  4. String args[] = {
  5. "--setuid=1000",
  6. "--setgid=1000",
  7. "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003",
  8. "--capabilities=130104352,130104352",
  9. "--runtime-init",
  10. "--nice-name=system_server",
  11. "com.android.server.SystemServer",
  12. };
  13. ZygoteConnection.Arguments parsedArgs = null;
  14. int pid;
  15. try {
  16. parsedArgs = new ZygoteConnection.Arguments(args); //把字符串数组中的参数转换为Arguments对象
  17. /*
  18. * Enable debugging of the system process if *either* the command line flags
  19. * indicate it should be debuggable or the ro.debuggable system property
  20. * is set to "1"
  21. */
  22. int debugFlags = parsedArgs.debugFlags;
  23. if ("1".equals(SystemProperties.get("ro.debuggable")))
  24. debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
  25. /* fork一个子进程作为SystemServer进程*/
  26. pid = Zygote.forkSystemServer(
  27. parsedArgs.uid, parsedArgs.gid,
  28. parsedArgs.gids, debugFlags, null,
  29. parsedArgs.permittedCapabilities,
  30. parsedArgs.effectiveCapabilities);
  31. } catch (IllegalArgumentException ex) {
  32. throw new RuntimeException(ex);
  33. }
  34. /* For child process */
  35. if (pid == 0) {
  36. //SystemServer进程的初始化设置
  37. handleSystemServerProcess(parsedArgs);
  38. }
  39. return true;
  40. }

Zygote进程通过Zygote.forkSystemServer函数来创建一个新的进程来启动SystemServer组件,返回值pid等0的地方就是新的进程要执行的路径,即新创建的进程会执行handleSystemServerProcess函数。

循环等待客户端的连接

Zygote进程介绍【转】
  1. private static void runSelectLoopMode() throws MethodAndArgsCaller {
  2. ArrayList<FileDescriptor> fds = new ArrayList(); //存储所有socket文件句柄
  3. ArrayList<ZygoteConnection> peers = new ArrayList();//存储所有客户端连接
  4. FileDescriptor[] fdArray = new FileDescriptor[4];
  5. fds.add(sServerSocket.getFileDescriptor());//添加服务端socket文件描述符到fds中
  6. peers.add(null);
  7. int loopCount = GC_LOOP_COUNT;
  8. while (true) {
  9. int index;
  10. /*
  11. * Call gc() before we block in select().
  12. * It's work that has to be done anyway, and it's better
  13. * to avoid making every child do it.  It will also
  14. * madvise() any free memory as a side-effect.
  15. *
  16. * Don't call it every time, because walking the entire
  17. * heap is a lot of overhead to free a few hundred bytes.
  18. */
  19. if (loopCount <= 0) {
  20. gc();
  21. loopCount = GC_LOOP_COUNT;
  22. } else {
  23. loopCount--;
  24. }
  25. try {
  26. fdArray = fds.toArray(fdArray);
  27. //selectReadable内部调用select,使用多路复用I/O模型
  28. //当有客户端连接时,selectReadable返回
  29. index = selectReadable(fdArray);
  30. } catch (IOException ex) {
  31. throw new RuntimeException("Error in select()", ex);
  32. }
  33. if (index < 0) {
  34. throw new RuntimeException("Error in select()");
  35. } else if (index == 0) {
  36. //有一个客户端连接
  37. ZygoteConnection newPeer = acceptCommandPeer();
  38. peers.add(newPeer);
  39. fds.add(newPeer.getFileDesciptor());
  40. } else {
  41. boolean done;
  42. //客户端发送了请求,peers.get(index)获取当前客户端的ZygoteConnection,并调用当前连接的runOnce()函数创建新的应用程序
  43. done = peers.get(index).runOnce();
  44. if (done) {
  45. peers.remove(index);
  46. fds.remove(index);
  47. }
  48. }
  49. }
  50. }

函数runSelectLoopMode()使Zygote进入非阻塞读取socket操作,函数selectReadable()用于监听服务端Socket文件描述是否有客户端的连接,该函数使用的是Linux多路I/O服务select系统调用:

  1. do {
  2. err = select (nfds, &fdset, NULL, NULL, NULL);
  3. } while (err < 0 && errno == EINTR);

当selectReadable返回-1时,表示内部错误;返回值为0时,表示没有可处理的连接;返回值大于0时,表示客户端连接的个数。

zygote总结:
1.创建AppRuntime对象,并调用它的start函数;
2.调用startVm创建Java虚拟机;
3.调用startReg函数来注册JNI函数;
4.调用ZygoteInit类的main函数,从此就进入了Java世界;
5.调用registerZygoteSocket 注册一个服务端socket;
6.调用preloadClasses 函数加载类资源;
7.调用preloadResources函数加载系统资源;
8.调用startSystemServer函数创建SystemServer进程;
9.调用runSelectLoopMode函数进入服务端socket监听;

Zygote孵化新进程

 

fork是Linux系统的系统调用,用于复制当前进程,产生一个新的进程。新进程被创建后,和父进程共享已经分配的内存空间,除了进程ID外,新进程拥有和父进程完全相同的进程信息,直到向内存写入数据时,操作系统才复制一份目标地址空间,并将要写的数据写入到新的地址空间中,这就是所谓的copy-on-write机制,这种机制最大限度地在多个进程*享物理内存。fork函数的返回值大于0时,代表的是父进程,当等于0时,代表的是被复制的子进程,父子进程的区分就是通过fork的返回值来区分。当一个客户端进程请求Zygote孵化一个新的进程时,Zygote首先会得到该客户端的Socket连接,并将该连接封装为ZygoteConnection对象,并调用该对象的runOnce()函数来fork出一个新进程:done = peers.get(index).runOnce();

  1. boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
  2. String args[];
  3. Arguments parsedArgs = null;
  4. FileDescriptor[] descriptors;
  5. try {
  6. args = readArgumentList();
  7. descriptors = mSocket.getAncillaryFileDescriptors();
  8. } catch (IOException ex) {
  9. Log.w(TAG, "IOException on command socket " + ex.getMessage());
  10. closeSocket();
  11. return true;
  12. }
  13. if (args == null) {
  14. // EOF reached.
  15. closeSocket();
  16. return true;
  17. }
  18. /** the stderr of the most recent request, if avail */
  19. PrintStream newStderr = null;
  20. if (descriptors != null && descriptors.length >= 3) {
  21. newStderr = new PrintStream(
  22. new FileOutputStream(descriptors[2]));
  23. }
  24. int pid = -1;
  25. FileDescriptor childPipeFd = null;
  26. FileDescriptor serverPipeFd = null;
  27. try {
  28. parsedArgs = new Arguments(args);
  29. applyUidSecurityPolicy(parsedArgs, peer);
  30. applyRlimitSecurityPolicy(parsedArgs, peer);
  31. applyCapabilitiesSecurityPolicy(parsedArgs, peer);
  32. applyInvokeWithSecurityPolicy(parsedArgs, peer);
  33. applyDebuggerSystemProperty(parsedArgs);
  34. applyInvokeWithSystemProperty(parsedArgs);
  35. int[][] rlimits = null;
  36. if (parsedArgs.rlimits != null) {
  37. rlimits = parsedArgs.rlimits.toArray(intArray2d);
  38. }
  39. if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
  40. FileDescriptor[] pipeFds = Libcore.os.pipe();
  41. childPipeFd = pipeFds[1];
  42. serverPipeFd = pipeFds[0];
  43. ZygoteInit.setCloseOnExec(serverPipeFd, true);
  44. }
  45. //复制新进程
  46. pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
  47. parsedArgs.gids, parsedArgs.debugFlags, rlimits);
  48. } catch (IOException ex) {
  49. logAndPrintError(newStderr, "Exception creating pipe", ex);
  50. } catch (ErrnoException ex) {
  51. logAndPrintError(newStderr, "Exception creating pipe", ex);
  52. } catch (IllegalArgumentException ex) {
  53. logAndPrintError(newStderr, "Invalid zygote arguments", ex);
  54. } catch (ZygoteSecurityException ex) {
  55. logAndPrintError(newStderr,
  56. "Zygote security policy prevents request: ", ex);
  57. }
  58. try {
  59. //区分父子进程
  60. if (pid == 0) {
  61. // in child
  62. IoUtils.closeQuietly(serverPipeFd);
  63. serverPipeFd = null;
  64. handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
  65. // should never get here, the child is expected to either
  66. // throw ZygoteInit.MethodAndArgsCaller or exec().
  67. return true;
  68. } else {
  69. // in parent...pid of < 0 means failure
  70. IoUtils.closeQuietly(childPipeFd);
  71. childPipeFd = null;
  72. return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
  73. }
  74. } finally {
  75. IoUtils.closeQuietly(childPipeFd);
  76. IoUtils.closeQuietly(serverPipeFd);
  77. }
  78. }

forkAndSpecialize函数调用nativeForkAndSpecialize函数来创建一个新进程,nativeForkAndSpecialize是一个Native函数,该函数最终调用fork来复制一个进程。当Zygote复制出新进程时,由于复制出的新进程与Zygote进程共享内存空间,而在Zygote进程中创建的服务端Socket是新进程不需要的,因此新创建的进程需要关闭该Socket服务端,并调用为新进程指定的类文件的main入口函数。

Zygote进程介绍【转】

普通进程启动

 
对应am工具,对应的脚本为:
  1. base=/system
  2. export CLASSPATH=$base/framework/am.jar
  3. exec app_process $base/bin com.android.commands.am.Am "$@"
Android将使用app_process启动的普通进程分工具进程和普通进程两种类型,通过传递命令行参数来设置,在启动虚拟机时,传递的参数不同:
  1. runtime.start("com.android.internal.os.RuntimeInit",application ? "application" : "tool");

对应工具进程,传递tool字符串,而对于普通进程则传递application字符串,同时进程启动的Java类为com.android.internal.os.RuntimeInit

  1. public static final void main(String[] argv) {
  2. if (argv.length == 2 && argv[1].equals("application")) {
  3. if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
  4. redirectLogStreams();
  5. } else {
  6. if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
  7. }
  8. commonInit();
  9. //通过JNI调用C++函数,更换Java入口
  10. nativeFinishInit();
  11. if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
  12. }

通过nativeFinishInit函数重新回到C++空间,并根据设置的启动Java类来更好入口函数

  1. static void com_android_internal_os_RuntimeInit_nativeFinishInit(JNIEnv* env, jobject clazz)
  2. {
  3. gCurRuntime->onStarted();
  4. }
  1. virtual void onStarted()
  2. {
  3. sp<ProcessState> proc = ProcessState::self();
  4. ALOGV("App process: starting thread pool.\n");
  5. //启动Binder线程池
  6. proc->startThreadPool();
  7. AndroidRuntime* ar = AndroidRuntime::getRuntime();
  8. //更换Java入口类,在app_process的main函数中,如果启动的不是zygote进程,就为虚拟机设置了启动类及启动参数
  9. ar->callMain(mClassName, mClass, mArgC, mArgV);
  10. IPCThreadState::self()->stopProcess();
  11. }

Java启动类的查找过程在onVmCreated(JNIEnv* env)函数中完成。对于am工具,其入口类为com.android.commands.am.Am.java,该类的main函数如下:

  1. public static void main(String[] args) {
  2. try {
  3. (new Am()).run(args);
  4. } catch (IllegalArgumentException e) {
  5. showUsage();
  6. System.err.println("Error: " + e.getMessage());
  7. } catch (Exception e) {
  8. e.printStackTrace(System.err);
  9. System.exit(1);
  10. }
  11. }

至此就启动了一个am进程。

上一篇:MYSQL进阶学习笔记四:MySQL存储过程之定义条件,处理过程及存储过程的管理!(视频序号:进阶_11,12)


下一篇:mysql存储过程定义者