Android启动流程_Zygote阶段
前言
上一篇文档中我们描述了 Android 启动中的 init 启动部分,本片文档将会继续 Android 启动流程的逻辑,继续梳理 Zygote 部分功能。
说明框架
对于 Zygote 进程,要从以下框架说明:
第一点,编译,zygote 运行的二进制编译文件
zygote32、zygote64 文件对应 app_process32、app_precess64 文件区别,对应的功能。
第二点,功能,zygote 的重要功能描述
从上面两个点展开说明,可以说到 AndroidRuntime 虚拟机、预加载资源、app_process 命令的使用等。
正文
对于 Zygote 进程的功能描述,准备从以下方面展开描述:
首先,会对于 Zygote 进程运行的二进制文件描述,从代码路径到编译结构展开,对 Zygote 进程的功能文件进行整理;
然后,整理 Zygote 进程在启动中的时序图。
接着从功能的角度对 Zygote 进程的运行进行整理,重要的部分详细描述
最后,总结 Zygote 进程的知识点。
在上一篇介绍 init 进程的文档中,我们提到在 init 的第二阶段会扫描系统路径下的 rc 文件,并解析执行所有的 rc 文件中定义功能。在 Android 中,很多的服务是在这个阶段被启动,作为系统的 Native 服务或者说是守护进程运行,在这些服务中,对于启动流程而言,必须要提到的一个服务进程就是 Zygote 进程。
1、rc 文件
Zygote 进程在 init 进程中以 service 的方式启动的。从 Android 5.0 开始,Zygote 还是有变动的,之前是直接放入 init.rc 中的代码块中,现在是放到了单独的文件中,通过 init.rc 中通过 “import” 的方式引入文件。
如下所示:
从上面的语句可以看到,init.rc 并不是直接引入某个固定的文件,而是根据属性 ro.zygote 的内容来引入不同的文件。这是因为从 Android 5.0 开始,Android 系统开始支持 64 位的编译,Zygote 进程本身也会有 32 位和 64 位版本的区别,因此,这里通过 ro.zygote 属性来控制启动不同版本的 Zyogte 进程。
ro.zygote 属性的取值可能有以下情况:zygote32zygote64zygote32_64zygote64_32
在系统路径 /system/core/rootdir 同级目录下,对应四个关于 zygote 的 rc 文件匹配 zygote 的启动。
/android/system/core/rootdir$ ls -l
-rw-r--r-- 1 domain users 959 Nov 9 2022 init.zygote32_64.rc
-rw-r--r-- 1 domain users 563 Nov 9 2022 init.zygote32.rc
-rw-r--r-- 1 domain users 981 Nov 9 2022 init.zygote64_32.rc
-rw-r--r-- 1 domain users 565 Nov 9 2022 init.zygote64.rc
那么现在我们的版本中对于 ro.zygote 属性值是如何定义的呢?如下所示:
项目:/ $ getprop | grep -i "ro.zygote"
[ro.zygote]: [zygote64_32]
现在的项目中使用的是 ro.zygote = zygote64_32,也就是对应使用 init.zygote64_32.rc 文件启动 Zygote 进程。
这四个 rc 文件对应了 Android 中支持的四种运行模式。
init.zygote32.rc : 纯32位模式
init.zygote32_64.rc : 混32位模式,即32位为主,64位为辅
init.zygote64.rc : 纯64位模式
init.zygote64_32.rc : 混64位模式,即64位为主,32位为辅
1.1 init.zygote32.rc
下面是 init.zygote32.rc 文件内容,系统使用此 rc 文件,zygote 将会以 32 位模式运行。
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks
1.2 init.zygote32_64.rc
下面是 init.zygote32_64.rc 文件文件内容,系统使用此 rc 文件,会启动两个 zygote 进程,32 位和 64 位两种服务,32 位为主,64 位为辅。
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygoteclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasksservice zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondaryclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote_secondary stream 660 root systemsocket usap_pool_secondary stream 660 root systemonrestart restart zygotewritepid /dev/cpuset/foreground/tasks
1.3 init.zygote64.rc
下面是 init.zygote64.rc 文件内容,系统使用此 rc 文件,zygote 将会运行在 64 位模式。
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks
1.4 init.zygote64_32.rc
下面是 init.zygote64_32.rc 文件内容,系统使用此 rc 文件,将会启动两个zygote 进程,64 位和 32 位,64 位为主,32 位为辅。
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygoteclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasksservice zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preloadclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote_secondary stream 660 root systemsocket usap_pool_secondary stream 660 root systemonrestart restart zygotewritepid /dev/cpuset/foreground/tasks
项目上现在使用的 rc 文件是 init.zygote64_32.rc 文件,我们来简单分析下文件内容
从 rc 文件中我们可以看出,定义了两个 zygote 服务:zygote 和 zygote_secondary。
zygote 是作为主服务,用于处理 64 位的程序,对应启动的可执行文件是 app_process64;
-
启动的参数是“-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote”;
-
定义了名为 zygote 的 Socket;
-
onrestart 指的是当 zygote 进程重启时,执行后面的指令,比如写入电源状态、重启系统服务等;
-
当创建子进程时,在 /dev/cpuset/foreground/tasks 里写入 pid。
zygote_secondary 是作为辅服务,用于处理 32 位的程序,对应启动的可执行文件是 app_process32;
-
启动的参数是“-Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload”;
-
定义了名为 zygote_secondary 的 Socket;
-
当 zygote_secondary 进程重启时,重启 zygote 服务;
-
当创建子进程时,在 /dev/cpuset/foreground/tasks 下写入 pid。
2、app_process
app_process 是启动 zygote 时执行的程序。app_process 是 Android 上一个原生程序,是 APP 进程的主入口点,不仅是说可以启动 zygote 进程,还可以启动任何其他的 APP,简单理解为是一个可以使用虚拟机运行 main 函数的程序。
2.1 代码路径
app_process 的代码路径位于 /android/frameworks/base/cmds/app_process 路径下。文件结构如下:
ubuntu16-017:~/workspace/src/android/frameworks/base/cmds/app_process$ ls -l
total 28
-rw-r--r-- 1 domain users 1992 Nov 9 2022 Android.mk
-rw-r--r-- 1 domain users 12019 Nov 9 2022 app_main.cpp
-rw-r--r-- 1 domain users 0 Nov 9 2022 MODULE_LICENSE_APACHE2
-rw-r--r-- 1 domain users 10695 Nov 9 2022 NOTICE
2.2 编译
编译文件是 /android/frameworks/base/cmds/app_process/Android.mk,下面我们来看下编译
LOCAL_PATH:= $(call my-dir)// 编译所需依赖库
app_process_common_shared_libs := \libandroid_runtime \libbinder \libcutils \libdl \libhidlbase \liblog \libnativeloader \libutils \# This is a list of libraries that need to be included in order to avoid
# bad apps. This prevents a library from having a mismatch when resolving
# new/delete from an app shared library.
# See b/21032018 for more details.
app_process_common_shared_libs += \libwilhelm \app_process_common_static_libs := \libsigchain \// 编译的文件
app_process_src_files := \app_main.cpp \app_process_cflags := \-Wall -Werror -Wunused -Wunreachable-codeapp_process_ldflags_32 := \-Wl,--version-script,art/sigchainlib/version-script32.txt -Wl,--export-dynamic
app_process_ldflags_64 := \-Wl,--version-script,art/sigchainlib/version-script64.txt -Wl,--export-dynamicinclude $(CLEAR_VARS)LOCAL_SRC_FILES:= $(app_process_src_files)LOCAL_LDFLAGS_32 := $(app_process_ldflags_32)
LOCAL_LDFLAGS_64 := $(app_process_ldflags_64)LOCAL_SHARED_LIBRARIES := $(app_process_common_shared_libs)LOCAL_WHOLE_STATIC_LIBRARIES := $(app_process_common_static_libs)// 编译模块名为 app_process
LOCAL_MODULE:= app_process
/*
* LOCAL_MULTILIB 可以指定模块编译 32 位或者 64 位或者都编译
* 值可选
* “both”: build both 32-bit and 64-bit.
* “32”: build only 32-bit.
* “64”: build only 64-bit.
* “first”: build for only the first arch (32-bit in 32-bit devices and 64-bit in 64-bit devices).
* “”: the default; the build system decides what arch to build based on the module class and other LOCAL_ variables, such as LOCAL_MODULE_TARGET_ARCH, LOCAL_32_BIT_ONLY, etc.
*/
LOCAL_MULTILIB := both
// 32 位可执行文件名为 app_process32
LOCAL_MODULE_STEM_32 := app_process32
// 64 位可执行文件名为 app_process64
LOCAL_MODULE_STEM_64 := app_process64LOCAL_CFLAGS += $(app_process_cflags)# In SANITIZE_LITE mode, we create the sanitized binary in a separate location (but reuse
# the same module). Using the same module also works around an issue with make: binaries
# that depend on sanitized libraries will be relinked, even if they set LOCAL_SANITIZE := never.
#
# Also pull in the asanwrapper helper.
ifeq ($(SANITIZE_LITE),true)
LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)/asan
LOCAL_REQUIRED_MODULES := asanwrapper
endif// 编译可执行文件
include $(BUILD_EXECUTABLE)# Create a symlink from app_process to app_process32 or 64
# depending on the target configuration.
ifneq ($(SANITIZE_LITE),true)
include $(BUILD_SYSTEM)/executable_prefer_symlink.mk
endif
2.3 使用介绍
从参数介绍到进程的启动,创建虚拟机,通过反射启动。
用法如下:
app_process [vm-options] [工作目录] [options] 类名 [类的main方法参数] [类的main方法参数] ....
参数如下:
vm-options – VM 选项
work-dir –工作目录(如/system/bin,/data/app,/data/...)
options –运行的参数 :–-zygote 启动 zygote 进程用的–-start-system-server–-application (api>=14) 启动应用程序–-nice-name=nice_proc_name (api>=14) (只有非--zygote模式下该选项才会生效)
[启动类名] –包含main方法的主类 (如com.android.internal.os.WrapperInit)
main-options –启动时候传递到main方法中的参数
到这里我们简单来概述一下,app_process 是用来启动程序的工具,可以启动 zygote 这样的系统服务,也可以启动 apk 这样的应用进程,也可以执行 java 程序。这里根据传入的参数,可以分为 zygote 模式和 非 zygote 模式。有以下注意点:
- 传入 –zygote 会启动 com.android.internal.os.ZygoteInit,否则启动 com.android.internal.os.RuntimeInit。
- –start-system-server 只在启动 zygote 时有效。
- 在非 zygote 模式中,有无 –application 的选项的区别只是是否将 stdout 和 stderr 重定向到 AndroidPrintStream。
- 只有在非 zygote 的情况下,–nice-name= 选项有效。
示例:
1、启动 zygote 进程命令:
app_process -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote2、启动 com.android.test 应用命令:
app_process -Djava.class.path=/sdcard/classes.dex /data/local/tmp --application --nice-name=helloservice com.android.test.HelloWorld 1 2 a2、启动 com.android.test 应用命令(通过 apk 的方式)
app_process -Djava.class.path=/sdcard/app.apk /data/local com.android.test.HelloWorld
2.4 代码分析
2.4.1 app_main
app_process 程序的执行文件是 app_main.cpp 文件,入口是 main() 函数。文件路径为:/android/frameworks/base/cmds/app_process/app_main.cpp。
#if defined(__LP64__)
// 如果为 64 位进程,则进程名为 "zygote64",否则为 "zygote"
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64";
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32";
static const char ZYGOTE_NICE_NAME[] = "zygote";
#endif-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------int main(int argc, char* const argv[])
{-----------------------------------------------------------------------------------------
1、创建 AppRuntime 对象,AppRuntime 类用于创建和初始化虚拟机。if (!LOG_NDEBUG) {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 对象AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));// Process command line arguments argv[0] = "/system/bin/app_process"// ignore argv[0] 跳过 argv[0] 参数argc--;argv++;-----------------------------------------------------------------------------------------
2、从给定参数中解析 Java-Option 参数,并把参数添加到 AppRuntime 中// Everything up to '--' or first non '-' arg goes to the vm.// 直到遇到 '-' 或第一个非 '-' 的参数为止的所有内容都将提供给虚拟机作为 options。// The first argument after the VM args is the "parent dir", which// is currently unused.//// After the parent dir, we expect one or more the following internal// arguments ://// --zygote : Start in zygote mode 启动到 zygote 模式// --start-system-server : Start the system server. 启动 system server// --application : Start in application (stand alone, non zygote) mode. 以应用程序模式启动 (独立启动, 非 zygote)// --nice-name : The nice name for this process. 给进程起一个名字//// 对于非 zygote 启动,这些参数后面将是主类名,所有其余的参数都传递给此类的 main 方法;// For non zygote starts, these arguments will be followed by// the main class name. All remaining arguments are passed to// the main method of this class.// // 对于 zygote 启动,所有剩余的参数都传递给 zygote 的 main 方法。// For zygote starts, all remaining arguments are passed to the zygote.// main function.//// Note that we must copy argument string values since we will rewrite the// entire argument block when we apply the nice name to argv0.//// As an exception to the above rule, anything in "spaced commands"// goes to the vm even though it has a space in it.const char* spaced_commands[] = { "-cp", "-classpath" };// Allow "spaced commands" to be succeeded by exactly 1 argument (regardless of -s).bool known_command = false;int i;for (i = 0; i < argc; i++) {if (known_command == true) {runtime.addOption(strdup(argv[i]));// The static analyzer gets upset that we don't ever free the above// string. Since the allocation is from main, leaking it doesn't seem// problematic. NOLINTNEXTLINEALOGV("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;}// 将参数添加到 AppRuntime 中runtime.addOption(strdup(argv[i]));// The static analyzer gets upset that we don't ever free the above// string. Since the allocation is from main, leaking it doesn't seem// problematic. NOLINTNEXTLINEALOGV("app_process main add option '%s'", argv[i]);}-----------------------------------------------------------------------------------------这里继续解析参数,将信息保存在 zygote、startSystemServer、application 等变量中。// Parse runtime arguments. Stop at first unrecognized option.bool zygote = false;bool startSystemServer = false;bool application = false;String8 niceName;String8 className;++i; // Skip unused "parent dir" argument.while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {zygote = true;niceName = ZYGOTE_NICE_NAME;} else if (strcmp(arg, "--start-system-server") == 0) {startSystemServer = true;} else if (strcmp(arg, "--application") == 0) {application = true;} else if (strncmp(arg, "--nice-name=", 12) == 0) {niceName.setTo(arg + 12);} else if (strncmp(arg, "--", 2) != 0) {className.setTo(arg);break;} else {--i;break;}}-----------------------------------------------------------------------------------------
3、准备 ZygoteInit 或 RuntimeInit 启动所需要的参数Vector<String8> args;// 没有处于 zygote 模式if (!className.isEmpty()) {// We're not in zygote mode, the only argument we need to pass// to RuntimeInit is the application argument.//// 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);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 {// We're in zygote mode.// className 为空,处于 zygote 模式// 创建 /data/dalvik-cache/ 目录maybeCreateDalvikCache();if (startSystemServer) {args.add(String8("start-system-server"));}char prop[PROP_VALUE_MAX];if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {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);// 获取支持的 abi 列表args.add(abiFlag);// In zygote mode, pass all remaining arguments to the zygote// main() method.// 在 zygote 模式下,将所有剩余参数传递给 zygote 的 main() 方法。for (; i < argc; ++i) {args.add(String8(argv[i]));}}-----------------------------------------------------------------------------------------
4、设置进程名称if (!niceName.isEmpty()) {runtime.setArgv0(niceName.string(), true /* setProcName */);}-----------------------------------------------------------------------------------------
5、启动 ZygoteInit 或者 RuntimeInit 程序。if (zygote) {// 启动 zygote 服务runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {// 启动 application 服务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.");}
}
那么这里总结 app_process 程序的功能:
- 初始化 AppRuntime 对象
- 从命令行中解析参数,并把参数都添加到 AppRuntime 中
- 判断启动功能,zygote、application,并且准备对应启动参数
- 设置进程名称
- 调用 AppRuntime.start 函数启动 zygote 或者 application
2.4.2 AndroidRuntime/AppRuntime
AppRuntime 直译的话叫做应用运行时
,开发时,我们写好 Java/Kotlin 代码,通过对应的编译器将代码编译为字节码,AppRuntime 的作用就是创建一个可以执行字节码的环境,这个环境主要由两部分内容构成:
- 一部分负责对象的创建与回收,譬如类的加载器,垃圾回收器等
- 一部分负责程序逻辑的运行,譬如即时编译系统,解释器等
AppRuntime 类定义在 app_main.cpp 中,继承了 AndroidRuntime 类,app_process 启动程序是会初始化一个 AndroidRuntime 对象,下面是 AndroidRuntime 对象的构造函数
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :mExitWithoutCleanup(false),mArgBlockStart(argBlockStart),mArgBlockLength(argBlockLength)
{// 初始化 skia 图形系统SkGraphics::Init();// Pre-allocate enough space to hold a fair number of options.// 预先分配空间来存放传入虚拟机的参数mOptions.setCapacity(20);// 每个进程只能初始化一次assert(gCurRuntime == NULL); // one per processgCurRuntime = this;
}
app_process 调用 AppRuntime.start 函数会调用到 AndroidRuntime 中,AppRuntime 是在 app_main.cpp 文件中定义的 AndroidRuntime 的子类。
AndroidRuntime 的代码在 /android/frameworks/base/core/jni/AndroidRuntime.cpp 文件中,下面来看下 start 函数的功能
// AndroidRuntime.cppvoid 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");for (size_t i = 0; i < options.size(); ++i) {if (options[i] == startSystemServer) {const int LOG_BOOT_PROGRESS_START = 3000;LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));}}// 获取 system 启动目录// 系统目录从环境变量 ANDROID_ROOT 中读取。如果说取失败,则默认设置目录为"/system"。如果连"/system"也没有,则 Zygote 进程会退出。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);}// 通过 jni_invocation.Init(NULL) 完成 jni 接口的初始化JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;// 创建虚拟机if (startVm(&mJavaVM, &env, zygote) != 0) {return;}// onVimCreate() 是一个虚函数,调用它实际上调用的是继承类的 AppRuntime 中的重载函数。onVmCreated(env);// 注册系统类 JNI 方法// startReg() 函数通过调用 register_jni_procs() 函数将全局的 gRegJNI 中的本地 JNI 函数在虚拟机中注册if (startReg(env) < 0) {ALOGE("Unable to register all android natives\n");return;}// 为启动 Java 类的 main 函数做准备jclass stringClass;jobjectArray strArray;jstring classNameStr;stringClass = env->FindClass("java/lang/String");assert(stringClass != NULL);// Java: strArray = new String[options.size() + 1];strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);assert(strArray != NULL);classNameStr = env->NewStringUTF(className);assert(classNameStr != NULL);// Java: strArray[0] = "com.android.internal.os.ZygoteInit";env->SetObjectArrayElement(strArray, 0, classNameStr);// 将相关参数收集至 options 中,下面会传递给 ZygoteInit// --start-system-server, --abi-list=arm64-v8a ...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);}// 转换为 JNI 格式类名:com/android/internal/os/ZygoteInitchar* slashClassName = toSlashClassName(className);jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);} else {// 通过 GetStaticMethodID 函数来获取 main() 方法的 idjmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");if (startMeth == NULL) {ALOGE("JavaVM unable to find main() in '%s'\n", className);} else {// 调用 ZygoteInit.main() 调用 Java 层方法;env->CallStaticVoidMethod(startClass, startMeth, strArray);}}free(slashClassName);ALOGD("Shutting down VM\n");// 关闭 Java 虚拟机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");
}
AndroidRuntime.start() 函数的功能如下:
- 创建了一个 JniInvocation 的实例,并且调用它的成员函数 init 来初始化 JNI 环境。
- startVm 函数创建虚拟机及对应的 JNI 接口,即 JavaVM 接口和 JNIEnv 接口。startVm 函数中主要是
设置虚拟机配置参数
以及创建虚拟机实例
,创建虚拟机后每一个进程应该具有一个 JavaVM 指针,而每一个线程都具有一个 JNIEnv 指针。 - startReg 函数注册系统 JNI 方法;
- 收集 options 参数,加载指定的 class
JavaVM(Java Virtual Machine)和 JNIEnv(Java Native Interface Environment)是 Java Native Interface(JNI)中两个重要的概念,它们在实现 Java 与本地代码之间的交互时起着重要的作用。JavaVM:JavaVM 是一个代表 Java 虚拟机的结构体指针,它提供了一系列 JNI 函数,可以用于创建 Java 虚拟机、获取当前 Java 虚拟机等操作。JavaVM 提供了一种机制,可以在本地方法中获取对 Java 虚拟机的访问,从而可以在本地代码中操作 Java 对象、调用 Java 方法等功能。JavaVM 的作用是为本地代码提供对 Java 虚拟机的操作接口,使得本地代码能够与 Java 虚拟机进行交互。JNIEnv:JNIEnv 是一个代表 JNI 环境的指针类型,它是通过 JavaVM 获取到的,每个线程都会有一个对应的 JNIEnv。JNIEnv 提供了一系列 JNI 函数,可以用于在本地代码中访问 Java 对象、调用 Java 方法、处理异常等操作。通过 JNIEnv,本地代码可以与 Java 代码进行通信,操作 Java 对象,并实现 Java 与本地代码之间的数据传递和交互。总的来说,JavaVM 提供了对 Java 虚拟机的操作接口,而 JNIEnv 则提供了 JNI 环境的相关操作接口,这两者协同工作,使得本地代码能够与 Java 代码进行交互,实现 Java 与本地代码的互通。
时序图
这里考虑把时序图放在前面,总结描述。
这里可以画一张 app_main 和 AndroidRuntime 的初始化和交互逻辑图,包括再到 JNI 并通过反射找到对应函数的时序。
3、zygote 启动
zygote 启动时传入的参数是:com.android.internal.os.ZygoteInit。那么在通过 app_process 程序启动时,最终会通过反射找到 ZygoteInit 文件作为 Zygote 的入口文件。
本节将会从 ZygoteInit 文件开始,描述 Zygote 在启动过程中提供的功能。
3.1 时序图
时序图如下所示:
时序图简要描述:
从时序图中我们可以大致整理 Zygote 进程在 Android 初始化过程中提供的功能。
首先第一点,预加载资源。Android 中有很多资源是可以共享的,在启动阶段进行预加载可以在后续的进程中共享资源,也就避免了后续再去加载的动作,这样对于后续进程的启动、运行有很大的提升。这里的预加载并没有一次性完成,而是在线程中分步完成。这里是一次修改变更,由原来在一个函数中完成资源的预加载变更为开启不同的子线程去加载不同的资源,等待子线程完成预加载之后,zygote 进程再去执行后续的动作。
第二点,初始化 ZygoteServer 类。ZygoteServer 会去创建 Zygote 所需的 Socket 对象,用于后续的 socket 通信。
第三点,fork SystemServer 进程。启动 SystemServer 服务。
第四点,开启循环监听 Socket 通信。
3.2 Zygote 启动流程
前面提到在 app_process 会找到 ZygoteInit 作为 Zygote 的入口文件,执行 main() 函数。
ZygoteInit 文件路径为:/android/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {-----------------------------------------------------------------------------------------
1、设置进程 group id;记录时间和 Trace 信息;启动 DDMS 虚拟机监控服务ZygoteServer zygoteServer = null;// Mark zygote start. This ensures that thread creation will throw// an error.// 调用此方法后,虚拟机会拒绝线程的创建,如果此时创建线程会产生异常ZygoteHooks.startZygoteNoThreadCreation();// Zygote goes into its own process group.// 设置 zygote 进程的 group idtry {Os.setpgid(0, 0);} catch (ErrnoException ex) {throw new RuntimeException("Failed to setpgid(0,0)", ex);}Runnable caller;try {// Report Zygote start time to tron unless it is a runtime restart// 记录一些时间信息,用于性能分析if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {MetricsLogger.histogram(null, "boot_zygote_init",(int) SystemClock.elapsedRealtime());}// 记录 trace 信息String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,Trace.TRACE_TAG_DALVIK);bootTimingsTraceLog.traceBegin("ZygoteInit");// 启动 DDMS 虚拟机监控调试服务RuntimeInit.enableDdms();-----------------------------------------------------------------------------------------
2、解析参数;预加载资源boolean startSystemServer = false;String zygoteSocketName = "zygote";String abiList = null;boolean enableLazyPreload = false;for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {// 判断是否开启 SystemServerstartSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {// 是否启动延时加载enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {// 获取 abi 类型,一个 CPU 对应一个 abiabiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {// 获取 zygote socket 的名字zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);}}// 通过 zygote socket 的名字判断是否是主 zygote。final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);// 没有指定 abi 会有异常if (abiList == null) {throw new RuntimeException("No ABI list supplied.");}// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.if (!enableLazyPreload) {bootTimingsTraceLog.traceBegin("ZygotePreload");EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());//multithread to do preload start//preload(bootTimingsTraceLog);// 预加载资源partialPreload(bootTimingsTraceLog);//multithread to do preload endEventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());bootTimingsTraceLog.traceEnd(); // ZygotePreload} else {Zygote.resetNicePriority();}bootTimingsTraceLog.traceEnd(); // ZygoteInit// Disable tracing so that forked processes do not inherit stale tracing tags from// Zygote.Trace.setTracingEnabled(false, 0);-----------------------------------------------------------------------------------------
3、初始化 ZygoteServer 服务类,用于注册 socket 监听;创建 SystemServer 进程;// 执行一些初始化操作Zygote.initNativeState(isPrimaryZygote);// 与 startZygoteNoThreadCreation 函数对应,虚拟机可以创建线程了ZygoteHooks.stopZygoteNoThreadCreation();// 初始化 ZygoteServer 进程,管理 socket 注册监听zygoteServer = new ZygoteServer(isPrimaryZygote);if (startSystemServer) {// fork SystemServer 进程Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);// {@code r == null} in the parent (zygote) process, and {@code r != null} in the// child (system_server) process.// 如果 r 为空,说明是父进程 zygote,无任何处理,继续执行// 如果 r 不为空,说明是子进程 SystemServer,启动后直接返回if (r != null) {r.run();return;}}-----------------------------------------------------------------------------------------
4、在线程中预加载资源,包括 Class、Resources、SharedLibraries、openGL 资源等。开启一个无限循环,处理 socket 信息;Log.i(TAG, "Accepting command socket connections");//multithread to do preload startif (!enableLazyPreload) {Thread loadClassThread = new Thread(new Runnable() {public void run() {// 在线程中预加载 Classes 资源preloadClasses();}});loadClassThread.start();Thread loadResourceThread = new Thread(new Runnable() {public void run() {// 在线程中预加载 Resources 资源preloadResources();}});loadResourceThread.start();Thread loadLibraryThread = new Thread(new Runnable() {public void run() {// 在线程中预加载 SharedLibraries 资源preloadSharedLibraries();// 在线程中预加载 openGL 资源maybePreloadGraphicsDriver();}});loadLibraryThread.start();try {loadClassThread.join();loadResourceThread.join();loadLibraryThread.join();// Do an initial gc to clean up after startupbootTimingsTraceLog.traceBegin("PostZygoteInitGC");gcAndFinalize();bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC} catch(Exception e){}}//multithread to do preload end// The select loop returns early in the child process after a fork and// loops forever in the zygote.// 调用 ZygoteServer 开启一个无限循环,处理 socket 信息caller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, "System zygote died with exception", ex);throw ex;} finally {if (zygoteServer != null) {zygoteServer.closeServerSocket();}}// We're in the child process and have exited the select loop. Proceed to execute the// command.// 执行新进程的主函数if (caller != null) {caller.run();}
}
说明点:
1、预加载资源由原来的全部加载变为线程中加载,可以提高运行效率,减少开机时间。控制参数是 --enable-lazy-preload,仅有在 init.zygote64_32.rc 中的 zygote64 中有此参数,说明在现有代码逻辑中,仅有 zygote64 才会在启动时预加载资源。
首先第一点,预加载资源。Android 中有很多资源是可以共享的,在启动阶段进行预加载可以在后续的进程中共享资源,也就避免了后续再去加载的动作,这样对于后续进程的启动、运行有很大的提升。这里的预加载并没有一次性完成,而是在线程中分步完成。这里是一次修改变更,由原来在一个函数中完成资源的预加载变更为开启不同的子线程去加载不同的资源,等待子线程完成预加载之后,zygote 进程再去执行后续的动作。
第二点,初始化 ZygoteServer 类。ZygoteServer 会去创建 Zygote 所需的 Socket 对象,用于后续的 socket 通信。
第三点,fork SystemServer 进程。启动 SystemServer 服务。
第四点,开启循环监听 Socket 通信。
3.2.1 预加载资源
预加载资源主要是进行一些 类、资源、共享库的预加载工作,以提升运行时效率。封装的函数包括 preloadClasses()、preloadResources()、preloadSharedLibraries() 等,我们来简单看一下对于这些资源是如何加载的。
private static void preloadClasses() {final VMRuntime runtime = VMRuntime.getRuntime();InputStream is;try {// /system/etc/preloaded-classesis = new FileInputStream(PRELOADED_CLASSES);} catch (FileNotFoundException e) {Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");return;}Log.i(TAG, "Preloading classes...");long startTime = SystemClock.uptimeMillis();// Drop root perms while running static initializers.final int reuid = Os.getuid();final int regid = Os.getgid();// We need to drop root perms only if we're already root. In the case of "wrapped"// processes (see WrapperInit), this function is called from an unprivileged uid// and gid.boolean droppedPriviliges = false;if (reuid == ROOT_UID && regid == ROOT_GID) {try {Os.setregid(ROOT_GID, UNPRIVILEGED_GID);Os.setreuid(ROOT_UID, UNPRIVILEGED_UID);} catch (ErrnoException ex) {throw new RuntimeException("Failed to drop root", ex);}droppedPriviliges = true;}// Alter the target heap utilization. With explicit GCs this// is not likely to have any effect.float defaultUtilization = runtime.getTargetHeapUtilization();runtime.setTargetHeapUtilization(0.8f);try {BufferedReader br =new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);int count = 0;String line;while ((line = br.readLine()) != null) {// Skip comments and blank lines.line = line.trim();if (line.startsWith("#") || line.equals("")) {continue;}Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);try {if (false) {Log.v(TAG, "Preloading " + line + "...");}// Load and explicitly initialize the given class. Use// Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups// (to derive the caller's class-loader). Use true to force initialization, and// null for the boot classpath class-loader (could as well cache the// class-loader of this class in a variable).Class.forName(line, true, null);count++;} catch (ClassNotFoundException e) {Log.w(TAG, "Class not found for preloading: " + line);} catch (UnsatisfiedLinkError e) {Log.w(TAG, "Problem preloading " + line + ": " + e);} catch (Throwable t) {Log.e(TAG, "Error preloading " + line + ".", t);if (t instanceof Error) {throw (Error) t;}if (t instanceof RuntimeException) {throw (RuntimeException) t;}throw new RuntimeException(t);}Trace.traceEnd(Trace.TRACE_TAG_DALVIK);}Log.i(TAG, "...preloaded " + count + " classes in "+ (SystemClock.uptimeMillis() - startTime) + "ms.");} catch (IOException e) {Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);} finally {IoUtils.closeQuietly(is);// Restore default.runtime.setTargetHeapUtilization(defaultUtilization);// Fill in dex caches with classes, fields, and methods brought in by preloading.Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");runtime.preloadDexCaches();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);// Bring back root. We'll need it later if we're in the zygote.if (droppedPriviliges) {try {Os.setreuid(ROOT_UID, ROOT_UID);Os.setregid(ROOT_GID, ROOT_GID);} catch (ErrnoException ex) {throw new RuntimeException("Failed to restore root", ex);}}}}
上面是加载 类 资源,加载逻辑是读取 /system/etc/preloaded-classes 文件,并通过 Class.forName() 方法逐行加载文件中声明的类。提前预加载系统常用的类可以提升运行效率,但是这个预加载的过程耗时等资源消耗比较多。在源码目录 /android/frameworks/base/config 下,存在 preloaded-classes 文件,存放着启动启动时需要加载的常用类。下面是部分资源加载内容:
android.app.Activity
android.app.ActivityManager$1
android.app.ActivityManager$AppTask
android.app.ActivityManager$MemoryInfo$1
android.app.ActivityManager$MemoryInfo
android.app.ActivityManager$OnUidImportanceListener
android.app.ActivityManager$RecentTaskInfo$1
android.app.ActivityManager$RecentTaskInfo
android.app.ActivityManager$RunningAppProcessInfo$1
android.app.ActivityManager$RunningAppProcessInfo
android.app.ActivityManager$RunningServiceInfo$1
android.app.ActivityManager$RunningServiceInfo
android.app.ActivityManager$RunningTaskInfo$1
android.app.ActivityManager$RunningTaskInfo
android.app.ActivityManager$TaskDescription$1
android.app.ActivityManager$TaskDescription
android.app.ActivityManager$UidObserver
android.app.ActivityManager
下面是预加载资源 preloadResources() 函数:
private static void preloadResources() {final VMRuntime runtime = VMRuntime.getRuntime();try {mResources = Resources.getSystem();mResources.startPreloading();if (PRELOAD_RESOURCES) {Log.i(TAG, "Preloading resources...");long startTime = SystemClock.uptimeMillis();TypedArray ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_drawables);int N = preloadDrawables(ar);ar.recycle();Log.i(TAG, "...preloaded " + N + " resources in "+ (SystemClock.uptimeMillis() - startTime) + "ms.");startTime = SystemClock.uptimeMillis();ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_color_state_lists);N = preloadColorStateLists(ar);ar.recycle();Log.i(TAG, "...preloaded " + N + " resources in "+ (SystemClock.uptimeMillis() - startTime) + "ms.");if (mResources.getBoolean(com.android.internal.R.bool.config_freeformWindowManagement)) {startTime = SystemClock.uptimeMillis();ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_freeform_multi_window_drawables);N = preloadDrawables(ar);ar.recycle();Log.i(TAG, "...preloaded " + N + " resource in "+ (SystemClock.uptimeMillis() - startTime) + "ms.");}}mResources.finishPreloading();} catch (RuntimeException e) {Log.w(TAG, "Failure preloading resources", e);}}
预加载资源是定义在 /android/frameworks/base/core/res/res/values/arrays.xml 文件下的,预加载资源如下:
com.android.internal.R.array.preloaded_drawables
com.android.internal.R.array.preloaded_color_state_lists
com.android.internal.R.bool.config_freeformWindowManagement
下面是 /android/frameworks/base/core/res/res/values/arrays.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><array name="preloaded_freeform_multi_window_drawables"><item>@drawable/decor_maximize_button_dark</item><item>@drawable/decor_maximize_button_light</item>
</array>
下面是预加载共享库 preloadSharedLibraries() 函数:
private static void preloadSharedLibraries() {Log.i(TAG, "Preloading shared libraries...");System.loadLibrary("android");System.loadLibrary("compiler_rt");System.loadLibrary("jnigraphics");}
预加载共享库函数是预加载了三个共享库:libandroid.so、libcompiler_rt.so 和 libjnigraphics.so。
3.2.2 初始化 ZygoteServer
Zygote 通过 ZygoteServer 封装了对于 socket 通信的操作。
下面是 ZygoteServer 初始化函数:
ZygoteServer(boolean isPrimaryZygote) {mUsapPoolEventFD = Zygote.getUsapPoolEventFD();// 这里通过 Zygote.createManagedSocketFromInitSocket 创建 LocalServerSocket 对象。if (isPrimaryZygote) {mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);mUsapPoolSocket =Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);} else {mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);mUsapPoolSocket =Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);}fetchUsapPoolPolicyProps();mUsapPoolSupported = true;}
ZygoteServer 函数创建 LocalServerSocket 对象来进行 Socket 通信。
static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {int fileDesc;final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;try {String env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);} catch (RuntimeException ex) {throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);}try {FileDescriptor fd = new FileDescriptor();fd.setInt$(fileDesc);return new LocalServerSocket(fd);} catch (IOException ex) {throw new RuntimeException("Error building socket from file descriptor: " + fileDesc, ex);}}
下面是 Android Socket 的通信架构图:
LocalSocket 就是作为客户端建立于服务端的连接,发送数据。LocalServerSocket 作为服务端使用,建立服务端的 socket 监听客户端请求。
3.2.3 复制 SystemServer 进程
Zygote 进程在会复制 SystemServer 继续 Android 的启动。调用函数是 forkSystemServer 函数。
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {long capabilities = posixCapabilitiesAsBits(OsConstants.CAP_IPC_LOCK,OsConstants.CAP_KILL,OsConstants.CAP_NET_ADMIN,OsConstants.CAP_NET_BIND_SERVICE,OsConstants.CAP_NET_BROADCAST,OsConstants.CAP_NET_RAW,OsConstants.CAP_SYS_MODULE,OsConstants.CAP_SYS_NICE,OsConstants.CAP_SYS_PTRACE,OsConstants.CAP_SYS_TIME,OsConstants.CAP_SYS_TTY_CONFIG,OsConstants.CAP_WAKE_ALARM,OsConstants.CAP_BLOCK_SUSPEND);/* Containers run without some capabilities, so drop any caps that are not available. */StructCapUserHeader header = new StructCapUserHeader(OsConstants._LINUX_CAPABILITY_VERSION_3, 0);StructCapUserData[] data;try {data = Os.capget(header);} catch (ErrnoException ex) {throw new RuntimeException("Failed to capget()", ex);}capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32);// 启动参数/* Hardcoded command line to start the system server */String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"+ "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010","--capabilities=" + capabilities + "," + capabilities,"--nice-name=system_server","--runtime-args","--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,"com.android.server.SystemServer",};ZygoteArguments parsedArgs = null;int pid;try {parsedArgs = new ZygoteArguments(args);Zygote.applyDebuggerSystemProperty(parsedArgs);Zygote.applyInvokeWithSystemProperty(parsedArgs);boolean profileSystemServer = SystemProperties.getBoolean("dalvik.vm.profilesystemserver", false);if (profileSystemServer) {parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;}// fork SystemServer 进程/* Request to fork the system server process */pid = Zygote.forkSystemServer(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids,parsedArgs.mRuntimeFlags,null,parsedArgs.mPermittedCapabilities,parsedArgs.mEffectiveCapabilities);} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);}// pid = 0 表示子进程,从这里开始进入 SystemServer 进程。/* For child process */if (pid == 0) {if (hasSecondZygote(abiList)) {waitForSecondaryZygote(socketName);}// 关闭并释放从 Zygote copy 过来的 socketzygoteServer.closeServerSocket(); // 完成新创建的 system_server 进程的剩余工作return handleSystemServerProcess(parsedArgs);}/*** 注意 fork() 函数式一次执行,两次返回(两个进程对同一程序的两次执行)。* pid > 0 说明还是父进程。pid = 0 说明进入了子进程* 所以这里的 return null 依旧会执行 */return null;}
从上面的启动参数可以看到,SystemServer 进程的 uid 和 gid 都是 1000,进程名是 system_server,其最后要加载的类名是 com.android.server.SystemServer。准备好一系列参数之后通过 ZygoteConnection.Arguments() 拼接,接着调用 Zygote.forkSystemServer() 方法真正的 fork 出子进程 system_server。
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {ZygoteHooks.preFork();// Resets nice priority for zygote process.resetNicePriority();// 这里最终调用 nativeForkSystemServer 去执行 fork 操作。int pid = nativeForkSystemServer(uid, gid, gids, runtimeFlags, rlimits,permittedCapabilities, effectiveCapabilities);// Enable tracing as soon as we enter the system_server.if (pid == 0) {Trace.setTracingEnabled(true, runtimeFlags);}ZygoteHooks.postForkCommon();return pid;}
最后的 fork() 操作是在 native 层完成的。再回到 ZygoteInit.forkSystemServer() 中执行 fork() 之后的逻辑处理。对于 SystemServer 进程来说,fork 函数返回值是 0,会继续执行 handleSystemServerProcess() 函数,执行 SystemServer 的逻辑。对于 Zygote 进程,会启动下一步内容。
3.2.4 开启循环进行 Socket 通信
Zygote 进程在复制 SystemServer 进程后,SystemServer 继续执行启动逻辑,Zygote 进程会调用 zygoteServer.runSelectLoop() 函数开启循环进行 Socket 通信。
Runnable runSelectLoop(String abiList) {ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();// mServerSocket 是之前在 Zygote 中创建的socketFDs.add(mZygoteSocket.getFileDescriptor());peers.add(null);while (true) {fetchUsapPoolPolicyPropsWithMinInterval();int[] usapPipeFDs = null;StructPollfd[] pollFDs = null;// Allocate enough space for the poll structs, taking into account// the state of the USAP pool for this Zygote (could be a// regular Zygote, a WebView Zygote, or an AppZygote).if (mUsapPoolEnabled) {usapPipeFDs = Zygote.getUsapPipeFDs();pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length];} else {pollFDs = new StructPollfd[socketFDs.size()];}/** For reasons of correctness the USAP pool pipe and event FDs* must be processed before the session and server sockets. This* is to ensure that the USAP pool accounting information is* accurate when handling other requests like API blacklist* exemptions.*/int pollIndex = 0;for (FileDescriptor socketFD : socketFDs) {pollFDs[pollIndex] = new StructPollfd();pollFDs[pollIndex].fd = socketFD;pollFDs[pollIndex].events = (short) POLLIN;++pollIndex;}final int usapPoolEventFDIndex = pollIndex;if (mUsapPoolEnabled) {pollFDs[pollIndex] = new StructPollfd();pollFDs[pollIndex].fd = mUsapPoolEventFD;pollFDs[pollIndex].events = (short) POLLIN;++pollIndex;for (int usapPipeFD : usapPipeFDs) {FileDescriptor managedFd = new FileDescriptor();managedFd.setInt$(usapPipeFD);pollFDs[pollIndex] = new StructPollfd();pollFDs[pollIndex].fd = managedFd;pollFDs[pollIndex].events = (short) POLLIN;++pollIndex;}}try {// 有事件来时往下执行,没有时就阻塞Os.poll(pollFDs, -1);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}boolean usapPoolFDRead = false;while (--pollIndex >= 0) {if ((pollFDs[pollIndex].revents & POLLIN) == 0) {continue;}if (pollIndex == 0) {// 有新客户端连接// Zygote server socketZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);socketFDs.add(newPeer.getFileDescriptor());} else if (pollIndex < usapPoolEventFDIndex) {// Session socket accepted from the Zygote server sockettry {// 处理客户端请求ZygoteConnection connection = peers.get(pollIndex);final Runnable command = connection.processOneCommand(this);// TODO (chriswailes): Is this extra check necessary?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.if (command != null) {throw new IllegalStateException("command != null");}// We don't know whether the remote side of the socket was closed or// not until we attempt to read from it from processOneCommand. This// shows up as a regular POLLIN event in our regular processing loop.if (connection.isClosedByPeer()) {connection.closeSocket();peers.remove(pollIndex);socketFDs.remove(pollIndex);}}} catch (Exception e) {if (!mIsForkChild) {// We're in the server so any exception here is one that has taken place// pre-fork while processing commands or reading / writing from the// control socket. Make a loud noise about any such exceptions so that// we know exactly what failed and why.Slog.e(TAG, "Exception executing zygote command: ", e);// Make sure the socket is closed so that the other end knows// immediately that something has gone wrong and doesn't time out// waiting for a response.ZygoteConnection conn = peers.remove(pollIndex);conn.closeSocket();socketFDs.remove(pollIndex);} else {// We're in the child so any exception caught here has happened post// fork and before we execute ActivityThread.main (or any other main()// method). Log the details of the exception and bring down the process.Log.e(TAG, "Caught post-fork exception in child process.", e);throw e;}} finally {// Reset the child flag, in the event that the child process is a child-// zygote. The flag will not be consulted this loop pass after the Runnable// is returned.mIsForkChild = false;}} else {// Either the USAP pool event FD or a USAP reporting pipe.// If this is the event FD the payload will be the number of USAPs removed.// If this is a reporting pipe FD the payload will be the PID of the USAP// that was just specialized.long messagePayload = -1;try {byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {DataInputStream inputStream =new DataInputStream(new ByteArrayInputStream(buffer));messagePayload = inputStream.readLong();} else {Log.e(TAG, "Incomplete read from USAP management FD of size "+ readBytes);continue;}} catch (Exception ex) {if (pollIndex == usapPoolEventFDIndex) {Log.e(TAG, "Failed to read from USAP pool event FD: "+ ex.getMessage());} else {Log.e(TAG, "Failed to read from USAP reporting pipe: "+ ex.getMessage());}continue;}if (pollIndex > usapPoolEventFDIndex) {Zygote.removeUsapTableEntry((int) messagePayload);}usapPoolFDRead = true;}}// Check to see if the USAP pool needs to be refilled.if (usapPoolFDRead) {int[] sessionSocketRawFDs =socketFDs.subList(1, socketFDs.size()).stream().mapToInt(fd -> fd.getInt$()).toArray();final Runnable command = fillUsapPool(sessionSocketRawFDs);if (command != null) {return command;}}}}
mServerSocket
是 ZygoteInit.main()
中一开始就建立的服务端 socket,用于处理客户端请求。一看到 while(true)
就肯定会有阻塞操作。Os.poll()
在有事件来时往下执行,否则就阻塞。当有客户端请求过来时,调用 ZygoteConnection.processOneCommand()
方法来处理。
Runnable processOneCommand(ZygoteServer zygoteServer) {String args[];ZygoteArguments parsedArgs = null;FileDescriptor[] descriptors;try {args = Zygote.readArgumentList(mSocketReader);// TODO (chriswailes): Remove this and add an assert.descriptors = mSocket.getAncillaryFileDescriptors();} catch (IOException ex) {throw new IllegalStateException("IOException on command socket", ex);}// readArgumentList returns null only when it has reached EOF with no available// data to read. This will only happen when the remote socket has disconnected.if (args == null) {isEof = true;return null;}int pid = -1;FileDescriptor childPipeFd = null;FileDescriptor serverPipeFd = null;parsedArgs = new ZygoteArguments(args);if (parsedArgs.mAbiListQuery) {handleAbiListQuery();return null;}if (parsedArgs.mPidQuery) {handlePidQuery();return null;}if (parsedArgs.mUsapPoolStatusSpecified) {return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);}if (parsedArgs.mPreloadDefault) {handlePreload();return null;}if (parsedArgs.mPreloadPackage != null) {handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);return null;}if (canPreloadApp() && parsedArgs.mPreloadApp != null) {byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);Parcel appInfoParcel = Parcel.obtain();appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);appInfoParcel.setDataPosition(0);ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);appInfoParcel.recycle();if (appInfo != null) {handlePreloadApp(appInfo);} else {throw new IllegalArgumentException("Failed to deserialize --preload-app");}return null;}if (parsedArgs.mApiBlacklistExemptions != null) {return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions);}if (parsedArgs.mHiddenApiAccessLogSampleRate != -1|| parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {return handleHiddenApiAccessLogSampleRate(zygoteServer,parsedArgs.mHiddenApiAccessLogSampleRate,parsedArgs.mHiddenApiAccessStatslogSampleRate);}if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {throw new ZygoteSecurityException("Client may not specify capabilities: "+ "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)+ ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));}Zygote.applyUidSecurityPolicy(parsedArgs, peer);Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);Zygote.applyDebuggerSystemProperty(parsedArgs);Zygote.applyInvokeWithSystemProperty(parsedArgs);int[][] rlimits = null;if (parsedArgs.mRLimits != null) {rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);}int[] fdsToIgnore = null;if (parsedArgs.mInvokeWith != null) {try {FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);childPipeFd = pipeFds[1];serverPipeFd = pipeFds[0];Os.fcntlInt(childPipeFd, F_SETFD, 0);fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};} catch (ErrnoException errnoEx) {throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);}}/*** In order to avoid leaking descriptors to the Zygote child,* the native code must close the two Zygote socket descriptors* in the child process before it switches from Zygote-root to* the UID and privileges of the application being launched.** In order to avoid "bad file descriptor" errors when the* two LocalSocket objects are closed, the Posix file* descriptors are released via a dup2() call which closes* the socket and substitutes an open descriptor to /dev/null.*/int [] fdsToClose = { -1, -1 };FileDescriptor fd = mSocket.getFileDescriptor();if (fd != null) {fdsToClose[0] = fd.getInt$();}fd = zygoteServer.getZygoteSocketFileDescriptor();if (fd != null) {fdsToClose[1] = fd.getInt$();}fd = null;// 这里调用 Zygote.forkAndSpecialize 去执行 fork 进程的逻辑。pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion);try {if (pid == 0) {// in childzygoteServer.setForkChild();zygoteServer.closeServerSocket();IoUtils.closeQuietly(serverPipeFd);serverPipeFd = null;return handleChildProc(parsedArgs, descriptors, childPipeFd,parsedArgs.mStartChildZygote);} 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;}} finally {IoUtils.closeQuietly(childPipeFd);IoUtils.closeQuietly(serverPipeFd);}}
这里的进程复制跟 SystemServer 进程的复制类似,SystemServer 进程复制调用的是 Zygote.forkSystemServer 函数,Socket 通信时进程的复制调用的是 Zygote.forkAndSpecialize 函数,在 native 层的是实现逻辑类似。通过对应的 Socket 指令去执行对应的功能。
对于进程的复制,可以从下图中对 init 进程复制进程、zygote 进程复制进程的联系:
总结
本篇文档主要描述了 Android 启动流程中 Zygote 进程的内容。描述了从 Zygote 的启动,app_process 程序的启动以及 Zygote 初始化、预加载、进程的赋值等内容。
相关文章:

Android启动流程_Zygote阶段
前言 上一篇文档中我们描述了 Android 启动中的 init 启动部分,本片文档将会继续 Android 启动流程的逻辑,继续梳理 Zygote 部分功能。 说明框架 对于 Zygote 进程,要从以下框架说明: 第一点,编译,zygo…...
2022NOIP比赛总结
种花 1.本题是一道前缀和优化加上枚举的问题。先考虑 C 因为 F 是 C 下边随便加一个点,所以只要求出 C 就求出了 F 。 注意到,并没有要求上下行一样,唯一的要求是 C 的两个横要隔一行,这就是问题的突破点,这题很明显…...

Leetcode 排序链表
这段代码的算法思想是 归并排序,一种适合链表的排序方法。它通过递归地将链表拆分成两部分,分别排序,然后合并已排序的部分,从而达到整体排序的目的。以下是代码的中文解释: 算法步骤: 找到链表的中点&…...
哈希函数简介
哈希函数是一种将任意大小的数据输入(通常称为“消息”)转换为固定大小的输出(称为“哈希值”或“摘要”)的算法。 主要特点: 1、输出固定长度 无论输入数据的大小如何,哈希函数的输出总是固定长度。例如…...
nginx------正向代理,反向代理生产,以及能否不使用代理详解
在生产环境中,选择使用正向代理还是反向代理取决于具体的应用场景和需求。下面详细解释这两种代理的用处以及为什么在不同情况下会选择它们。 正向代理 (Forward Proxy) 用途 匿名访问: 隐藏客户端的真实 IP 地址,提供隐私保护。常用于绕过…...
iptables限制docker端口禁止某台主机访问(使用DOCKER链和raw表的PREROUTING链)
背景: 在Linux上docker映射了端口,想着对服务端口进行限制指定IP访问,发现在filter表的INPUT链限制无效 环境: 主机192.168.56.132上的docker容器部署了nginx并将容器80端口映射到主机8000端口 [rootlocalhost ~]# docker ps …...

【VM实战】VMware迁移到VirtualBox
VMware 虚拟机开机卸载VMware Tools 调整虚拟磁盘 对于Windows 10及以上的虚拟机,一般VMware默认都会选Nvme固态硬盘。在导出前必须将其改为SATA,否则VirtualBox导入会报Appliance Import错误 (E_INVALIDARG 0x80070057) 先删掉当前盘的挂载ÿ…...
Android WebView加载不到cookie
以下配置根据需求酌情添加,建议逐个试验,cookie操作不是内存操作,建议修改配置后卸载app再重新运行防止缓存影响测试结果。 1.设置应用程序的 WebView 实例是否应发送并接受 Cookie CookieManager cookieManager CookieManager.getInstanc…...
c++qt
1.显示画布 #include "code.h" #include <QtWidgets/QApplication> #include<iostream> #include<vector> #include <QWindow> #include <QGraphicsView> #include <QGraphicsScene>using namespace std;//1.空格 2.墙 3.入口…...
零跑汽车嵌入式面试题汇总及参考答案
C++ 的三大特性是什么? C++ 的三大特性分别是封装、继承和多态。 封装 概念:封装是把数据和操作数据的函数绑定在一起,对数据的访问进行限制。通过将数据成员声明为私有或保护,只允许通过公共的成员函数来访问和修改数据,从而隐藏了类的内部实现细节。这有助于提高代码的安…...

LC:贪心题解
文章目录 376. 摆动序列 376. 摆动序列 题目链接:https://leetcode.cn/problems/wiggle-subsequence/description/ 这个题目自己首先想到的是动态规划解题,贪心解法真的非常妙,参考下面题解:https://leetcode.cn/problems/wiggle…...

ubuntu交叉编译dbus库给arm平台使用
1.下载dbus库源码 https://www.freedesktop.org/wiki/Software/dbus 克隆源码: https://gitlab.freedesktop.org/dbus/dbus/-/tree/dbus-1.12?ref_type=heads 下载1.12.20版本: 指定pkgconfig环境变量: export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$PWD/../expat-2.3.…...

ansible开局配置-openEuler
ansible干啥用的就不多介绍了,这篇文章主要在说ansible的安装、开局配置、免密登录。 ansible安装 查看系统版本 cat /etc/openEuler-latest输出内容如下: openeulerversionopenEuler-24.03-LTS compiletime2024-05-27-21-31-28 gccversion12.3.1-30.…...

连锁收银系统的优势与挑战
在快速发展的零售环境中,连锁收银系统不仅是收银的工具,更是现代零售管理的重要组成部分。它在提升效率、优化客户体验以及数据管理等方面发挥了关键作用。然而,随着技术的进步和市场环境的变化,连锁收银系统也面临着诸多挑战。本…...
轻型民用无人驾驶航空器安全操控理论培训知识总结-多旋翼部分
航空器知识 螺旋桨 螺旋桨为多旋翼民用无人驾驶航空器提供升力,多旋翼民用无人驾驶航空器通过飞控系统控制电机调节螺旋桨转速,来实现飞行。 天线 多旋翼民用无人驾驶航空器的图像传输以及遥控控制信号,主要是通过无线信道进行的,靠民用无人驾驶航空器与遥控器的天线传…...

springboot092安康旅游网站的设计与实现(论文+源码)_kaic
毕业设计(论文) 基于JSP的安康旅游网站的设计与实现 姓 名 学 号 院 系 专 业 指导老师 2021 年 月 教务处制 目 录 目 录 摘 要 Abstract 第一章 绪论 1.1 研究现状 1.2 设…...
优化 Git 管理:提升协作效率的最佳实践20241030
优化 Git 管理:提升协作效率的最佳实践 引言 在现代软件开发中,版本控制系统是确保代码质量和团队协作顺畅的基石。Git 作为最流行的分布式版本控制工具,其灵活性和强大功能使得开发者能够高效地管理项目代码。然而,仅依靠工具本…...
Cocos使用精灵组件显示相机内容
Cocos使用精灵组件显示相机内容 1. 为什么使用精灵渲染 在游戏引擎中,游戏场景内除webview和video外所有的节点都是渲染在Canvas上,这导致了webview和video只能存在于所有节点的最上层或最下层,而这种层级关系会出现节点事件无法正常监听或者…...

AListFlutter(手机alist)——一键安装,可在手机/电视上运行并挂载各个网盘
前面提到软路由系统OpenWRT的时候,当时说过可以在OpenWRT里安装alist,然后挂载网盘,这样就可以通过webdav的方式在家庭局域网下的任何设备都可以访问操作这些网盘,摆脱硬盘空间不够的问题。 但alist的官方版本是没有手机版本的&a…...

2024快手面试算法题-生气传染
问题描述 思路分析 生气只会向后传播,最后一个生气的人一定是最长连续没有生气的人中的最后一个人,前提是前面得有一个人生气。 注意,一次只能传播一个人,比如示例1,第一次只会传播给第一个P,不会传播给第…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...