Android 系统启动之 zygote 进程启动分析二

本文基于 AOSP android-10.0.0_r41 版本讲解

前面我们说到,init 进程会解析 init.rc 文件,执行对应的命令,其中一个重要的任务就是启动 Zygote 进程。接下来我们就来分析 Zygote 进程的启动和执行过程。

上一节最后我们分析到调用 runtime.start() 启动 ZygoteInit Java 类:

cpp 复制代码
// AppRuntime 的初始化
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));

// 启动 ZygoteInit 类
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

这里初始化一个 AppRuntime 对象,然后通过这个对象的 start 方法启动一个 ZygoteInit Java 类。

AppRuntime 直译的话叫做应用运行时,开发时,我们写好 Java/Kotlin 代码,通过对应的编译器将代码编译为字节码,AppRuntime 的作用就是创建一个可以执行字节码的环境,这个环境主要由两部分内容构成:

  • 一部分负责对象的创建与回收,譬如类的加载器,垃圾回收器等
  • 一部分负责程序逻辑的运行,譬如即时编译系统,解释器等

我们先看下 AppRuntime 类的实现:

cpp 复制代码
class AppRuntime : public AndroidRuntime
{
public:
    AppRuntime(char* argBlockStart, const size_t argBlockLength)
        : AndroidRuntime(argBlockStart, argBlockLength)
        , mClass(NULL)
    {
    }

    //......
    // 省略一些代码

    String8 mClassName;
    Vector<String8> mArgs;
    jclass mClass;
};

先看 AppRuntime 的构造函数,调用父类 AndroidRuntime 构造函数:

cpp 复制代码
// AndroidRuntime 类在一个进程中只有一个实例对象,保存在全局变量 gCurRuntime 中。
static AndroidRuntime* gCurRuntime = NULL;

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 process
    // 全局 static 变量
    gCurRuntime = this;
}

构造函数中主要初始化 skia 图形系统,然后把 AndroidRuntime 保存到全局变量 gCurRuntime 中。

初始化完成后,就会调用 start 函数启动虚拟机:

cpp 复制代码
// 启动 ZygoteInit 类
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

这个函数比较长,我们逐步分析:

cpp 复制代码
//具体实现
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{   

    // 打印启动 log
    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
            className != NULL ? className : "(unknown)", getuid());

当这个 Log 打印时,标志着 Java 的运行时开始启动,如果 Android 的系统 log 中反复出现这段内容,而输出 ID都是Zygote,则说明系统可能出现问题,Zygote进程在不断地重启.

cpp 复制代码
    static const String8 startSystemServer("start-system-server");

    /*
     * 'startSystemServer == true' means runtime is obsolete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }

这里处理参数的异常,对于早期版本的 Runtime,才需要 startSystemServer == true 这样的参数,如果这里出现了这样的参数,打印一些 Log 通知开发人员。

cpp 复制代码
    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /system does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

这里从环境变量 ANDROID_ROOT 中读取到根目录,如果没有读到,则默认设置为 /system,如果 /system 目录不存在,直接退出。

cpp 复制代码
    const char* runtimeRootDir = getenv("ANDROID_RUNTIME_ROOT");
    if (runtimeRootDir == NULL) {
        LOG_FATAL("No runtime directory specified with ANDROID_RUNTIME_ROOT environment variable.");
        return;
    }

    const char* tzdataRootDir = getenv("ANDROID_TZDATA_ROOT");
    if (tzdataRootDir == NULL) {
        LOG_FATAL("No tz data directory specified with ANDROID_TZDATA_ROOT environment variable.");
        return;
    }

这里接着读取环境遍历。

cpp 复制代码
    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
    //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

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

这里调用 startVm 启动虚拟机。

cpp 复制代码
    onVmCreated(env);

onVmCreated 是一个虚函数,实际上调用的是子类 AppRuntime 中的重载函数:

cpp 复制代码
    virtual void onVmCreated(JNIEnv* env)
    {
        if (mClassName.isEmpty()) {
            return; // Zygote. Nothing to do here.
        }

    
        char* slashClassName = toSlashClassName(mClassName.string());
        mClass = env->FindClass(slashClassName);
        if (mClass == NULL) {
            ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
        }
        free(slashClassName);

        mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
    }

如果是 Zygote 进程,变量 mClassName 的值会为 null,会立刻返回。如果是一个普通 Java 类的调用, mClassName 中会存放类的名称,toSlashClassName(mClassName.string()) 的作用是将类名转换为类的全限定名,接着回去到类对应的 jclass 对象。

接着看 start 函数的后续部分:

cpp 复制代码
    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

startReg 的具体实现如下:

cpp 复制代码
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ATRACE_NAME("RegisterAndroidNatives");
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

// 进一步调用 register_jni_procs
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_util_Log),
    REG_JNI(register_android_util_MemoryIntArray),
    REG_JNI(register_android_util_PathParser),
    REG_JNI(register_android_util_StatsLog),
    REG_JNI(register_android_util_StatsLogInternal),
    REG_JNI(register_android_app_admin_SecurityLog),
    REG_JNI(register_android_content_AssetManager),
    REG_JNI(register_android_content_StringBlock),
    REG_JNI(register_android_content_XmlBlock),
    REG_JNI(register_android_content_res_ApkAssets),
    REG_JNI(register_android_text_AndroidCharacter),
    REG_JNI(register_android_text_Hyphenator),
    REG_JNI(register_android_view_InputDevice),
    REG_JNI(register_android_view_KeyCharacterMap),
    REG_JNI(register_android_os_Process),
    REG_JNI(register_android_os_SystemProperties),
    REG_JNI(register_android_os_Binder),
    REG_JNI(register_android_os_Parcel),
    REG_JNI(register_android_os_HidlSupport),
    REG_JNI(register_android_os_HwBinder),
    REG_JNI(register_android_os_HwBlob),
    REG_JNI(register_android_os_HwParcel),
    REG_JNI(register_android_os_HwRemoteBinder),
    REG_JNI(register_android_os_NativeHandle),
    // ...... 省略大部分
};

startReg 通过调用 register_jni_procs 函数将全局数组 gRegJNI 中的 JNI 本地函数注册到虚拟机中。我们在 Java 层中调用的很多 Native 方法就是在这里注册的。

接着看 start 函数的后续部分:

cpp 复制代码
    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

这里主要是准备执行 Java 类的参数。这些参数保存在 strArray 中。

cpp 复制代码
    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}

先找到 ZygoteInit 类对应的 jclass, 然后通过 GetStaticMethodID 获得ZygoteInit 类中 main 方法的 ID,接着调用 CallStaticVoidMethod 函数 main 函数。至此,整个 start 函数就分析完了,Zygote进程的初始化过程将转到Java层了。

参考资料

相关推荐
aaajj3 分钟前
【Android】获取设备音频
android
清泓y1 小时前
UE移动开发技术面试题
android·面试·ue5·ue4·游戏程序
DevRen1 小时前
Compose Snapshot 系统详解
android
艾莉丝努力练剑4 小时前
【MYSQL】MYSQL学习的一大重点:基本查询(下)
android·数据库·学习·mysql·面试·八股文
三少爷的鞋4 小时前
Android 中coroutineScope 和 supervisorScope 到底怎么选?先搞清楚 Exception 和 Result
android
其实防守也摸鱼6 小时前
KMP全栈开发:从Android到AI Agent的技术演进与实践
android·运维·网络·人工智能·学习·安全·macos
YM52e11 小时前
鸿蒙Flutter Padding内边距:EdgeInsets详解
android·学习·flutter·华为·harmonyos·鸿蒙
Rex叶然15 小时前
ScreenMatch适配安卓屏幕脚本,用于老项目
android·screenmatch·安卓屏幕适配
阿pin15 小时前
Android随笔-Activity启动流程深度分析
android·activity启动流程
zzq779716 小时前
加固包闪退四象限定位:targetSdk 与保护策略实战解析
android·安全·安卓·安全架构