深入Android系统(七)Zygote进程

5,325 阅读16分钟

ZygoteAndroid中非常重要的一个进程,它和Init进程SystemServer进程Android中有着不可替代的地位。

Zygote简介

Linux的进程是通过系统调用fork产生的,fork出的子进程除了内核中的一些核心的数据结构和父进程不相同外,其余的内存映像都是和父进程共享的。只有当子进程需要去改写这些共享的内存时,操作系统才会为子进程分配一个新页面。这就是所谓的写时复制(Copy On Write)

通常子进程fork出后,会继续执行系统调用execexec将用一个新的可执行文件的内容替换当前进程的代码段数据段

forkexecLinux启动应用的标准做法,Init进程也是这样来启动各种服务的。

不过Zygote创建应用程序时却只是用了fork,没有调用exec

首先,Android应用中跑的的是虚拟机,虚拟机中Java代码的不同才造成了应用的区别,而对于基础的运行环境,要求却是一样的。

其次,Zygote在初始化时就会会创建虚拟机,同时把需要的系统类库和资源文件加载的内存中。而Zygotefork出子进程后,这个子进程也会得到一个已经加载好基础资源的虚拟机

这样,接下来只需要装载APK相关的资源就可以运行了,可以做到提升效率

Zygote进程的初始化

Zygote进程在Init进程中以service的方式启动的。我们来看下它在init.rc中的配置内容:

import /init.${ro.zygote}.rc
on late-init
    ......
    # Now we can start zygote for devices with file based encryption
    # 这里触发启动 zygote
    trigger zygote-start
on zygote-start && (*) //*用来省略一些属性值的判断
    start netd
    start zygote
    start zygote_secondary

Android 5.0开始,Android开始支持64位编译,Zygote本身也会有32位64位的区别,因此,通过ro.zygote属性来控制启动不同版本的Zygote进程。在9.0的源码中,存在4个相关文件:

init.zygote32.rc
init.zygote32_64.rc
init.zygote64.rc
init.zygote64_32.rc

挑个特殊的看下(init.zygote32_64.rc):

service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    ......
service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    ......
  • 从文件内容中可以看到定义了两个Zygote服务:zygotezygote_secondary
  • 两个服务最大的区别是可执行文件不同:一个是app_process32、一个是app_process64
  • init.zygote64_32.rc文件就不贴出来了,其实就是把两个可执行文件交换一下
  • 对于init.zygote32.rcinit.zygote64.rc分别只有一个Zygote服务

从这里我们就可以知道,Android将会支持4中运行模式:

  • 纯32位模式:ro.zygote的值为zygote32
  • 32位模式为主,64位为辅:ro.zygote的值为zygote32_64
  • 纯64位模式:ro.zygote的值为zygote64
  • 64位模式为主,32位为辅:ro.zygote的值为zygote64_32

而对于可执行文件app_process,源文件路径在frameworks/base/cmds/app_process,不过在学习源码前,我们先来看下app_process用法

app_process的用法

上面我们知道Zygote的启动是通过app_process来实现的

app_process主要作用是解析启动参数,然后根据启动参数选择不同的启动模式

为了更好的理解app_process,我们先来看明白它是怎么使用的。不过关于app_process参数的使用说明,源码中却只有:

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

有些太简洁了哈。。。。

我们还是来看几个示例吧,以Zygote为例:

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

再来一个有趣版本(下面这段指令的目的是:运行一个Application,包含main函数类的名称为Test):

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

结合源码中的Usage(纯净版)...和这两个例子,我们可以这么总结:

  • -Xzygote-Djava.class.p**:属于[java-options],这些参数会传给虚拟机,并且参数必须以-开头,一旦遇到非-或者--,表示[java-options]结束
  • /system/bin/data/lee:属于cmd-dir,程序的运行目录,随便指定即可,文件操作时会以此为当前路径,正经的大多运行在/system/bin
  • Test:属于start-class-name,声明入口类的名称,有包名的话需要加上包名
  • --zygote:属于[options],这些参数都以符号--开头。参数--zygote表示要启动Zygote进程

app_processmain()

有了上面app_process用法的铺垫,我们再来分析main函数就会更容易些,老样子,先看整体:

int main(int argc, char* const argv[])
{
    // 1. 创建 AppRuntime 对象
    
    // 2. 保存 Java-Option 参数

    // 3. 解析 Option 参数:--开头的那种
    
    // 4. 根据参数的解析结果,准备ZygoteInit或者RuntimeInit相关的参数
    
    // 5. 将本进程名称改为nice-name指定的字符串
    
    // 6. 根据参数的解析结果,启动对应的Java类
}

很清晰了然了哈,我们再看下具体的源码

创建AppRuntime对象

main()源码:

AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
  • AppRuntime是在app_process中定义的类,继承了系统的AndroidRuntime类。
  • AndroidRuntime类是底层很重要很重要的一个类,主要作用是创建和初始化虚拟机,等下详细分析
  • 整个main()的后续流程都是通过这个runtime来操作的

保存Java-Option参数

    ......
    for (i = 0; i < argc; i++) {
        // 省略know_command相关
        ......
        if (argv[i][0] != '-') { 
            //如果不是-开头的话,解析结束
            //按照app_process的参数规则:java-option后面是cmd-dir,不是以-开头的
            //所以,正常情况解析到java-option就会停止
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            //如果是--开头并且后面是空字符时,结束解析
            //不知道加这个的目的是干啥
            ++i; // Skip --.
            break;
        }
        runtime.addOption(strdup(argv[i]));
        // 省略打印信息
        ......
    }
    ......

解析Option参数

Option参数在源码中称为runtime argument

    //需要解析出来的参数都在这里了
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;
    
    // 按照参数的规则,上面java-option解析完后,紧接着是cmd-dir,也就是parent dir
    // 对于cmd dir不需要做额外处理,直接跳过
    ++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) {
            //如果不是--开头,就认为是class name
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }

准备ZygoteInit或者RuntimeInit需要的参数

    //定义一个字符参数集合
    Vector<String8> args;
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        // 非Zygote模式,没有删掉官方注释,开心不
        // 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);
        // 省略打印部分
        ......
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();
        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }
        // 省略ABI属性值的获取
        ......
        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);
        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        // Zygote模式,app_process的参数会被统一打包进参数集合中
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }

将进程名称改为nice-name指定的字符串

    if (!niceName.isEmpty()) {
        // 这是AndroidRuntime提供的函数,设置进程名称
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }
    
    // 在AndroidRuntime.cpp中
    void AndroidRuntime::setArgv0(const char* argv0, bool setProcName) {
        if (setProcName) {
            int len = strlen(argv0);
            if (len < 15) {
                pthread_setname_np(pthread_self(), argv0);
            } else {
                pthread_setname_np(pthread_self(), argv0 + len - 15);
            }
        }
        memset(mArgBlockStart, 0, mArgBlockLength);
        strlcpy(mArgBlockStart, argv0, mArgBlockLength);
    }

启动对应的Java

    if (zygote) {
        // 如果是--zygote,则执行ZygoteInit类,启动Zygote进程
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        // 如果是指定了class name,则通过RuntimeInit执行传进来的类
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }

app_processAndroid中的应用

通过上面的源码分析我们知道:app_process除了能启动Zygote进程,也可以使用它来执行某个系统的Java

Android中的常用工具am就是一个很好的例子:

am是一个发送Intent的工具,像am startam broadcast等。

但是,am实际上只是一个包含几行代码的脚本文件,它的功能都是通过调用app_process来完成的

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

有木有,有木有,Android还是蛮惊喜的,哈哈哈!

大家可以仔细阅读下Am.java这个类,很有特点,调用关系找起来是真滴费神,加油哟!

启动虚拟机-AndroidRuntime

在分析app_processmain()时我们知道AppRuntime继承的AndroidRuntime类。

  • AndroidRuntime类是一个很重要的类,它负责启动虚拟机以及Java线程
  • AndroidRuntime类在一个进程中只有一个实例对象,保存在全局变量gCurRuntime

构造函数

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 process
    gCurRuntime = this;
}

启动虚拟机

本章只是简单介绍下虚拟机的启动过程,具体的实现细节后面单独拉出一篇来学习

app_processmain()函数中,最后调用了runtime.start()来执行Java类,这个start()函数就是在AndroidRuntime中定义的。代码比较长,我们还是先看整体流程:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    // 1. 打印启动log
    
    // 2. 获取系统目录 

    // 3. 启动虚拟机

    // 4. 调用onVmCreated函数
    
    // 5. 注册系统的JNI函数
    
    // 6. 准备Java类的main函数的相关参数
    
    // 7. 调用ZygoteInit类的main方法

}

打印启动Log

源码片段:

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

我们再看下系统启动过程的Log片段:

00:00:21.226808  3244  3244 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<
  • 这段Log标志着Android系统的启动
  • 因为以后的应用进程都是从Zygote进程fork出来的,所以后面不会再执行start()函数了
  • 如果Android的系统log中反复出现这段内容,而输出ID都是Zygote,则说明系统可能出现问题,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);
    }
  • 系统目录从环境变量ANDROID_ROOT读取
  • 如果没有设置,则默认设置为/system
    • 系统目录是在Init进程中创建出来
  • 如果/system目录不存在,直接退出

启动虚拟机

源码片段

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

通过startVm启动虚拟机,我们先往下看,后面会详细介绍。

调用onVmCreated函数

    onVmCreated(env);

onVmCreated函数是一个虚函数,对于app_process来说实际上调用的是AppRuntime的重载函数,下面是AppRuntime中的代码逻辑:

    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));
    }

AppRuntimeonVmCreated函数中:

  • 如果是Zygote进程,变量mClassName的值会为null,会立刻返回
  • 如果是一个普通Java类的调用,mClassName中会存放类的名称
  • toSlashClassName(mClassName.string())的作用是将类名转换为类的全限定名
    • 类名com.android.Zygote转化为全限定名就变为/com/android/Zygote

注册系统的JNI函数

源码片段

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

startReg通过调用register_jni_procs函数将全局数组gRegJNI中的JNI本地函数注册到虚拟机中:

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ......
    // 这个函数会修改当前进程的全局变量gCreateThreadFn函数指针为 javaCreateThreadEtc()
    // 此之后那么新建的子线程便会
    // 先执行javaCreateThreadEtc() -> javaThreadShell() -> javaAttachThread() -> jvm->AttachCurrentThread() 
    // 此时从普通C层线程成为JVM线程
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    ......
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    ......
}

而对于gRegJNI数组,格式为

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

省略了很多元素,该数组中每一个成员都代表一类文件的JNI映射,其中REG_JNI是一个宏定义,该宏的作用就是调用相应的JNI注册方法

准备Java类的main函数的相关参数

源码片段

    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);
    }

上面是非常经典的在Native层创建Java层对象的操作:

  • 创建一个java.lang.String的数组对象
  • 并根据传入的参数对数组对象逐个元素进行赋值

调用ZygoteInit类的main方法

源码片段

    // 转化为全限定名
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    // 获取类的class对象
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            // 执行main函数
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
    // 此处main函数执行完毕,执行一些结束操作
    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");

调用main方法前:

  • 先通过GetStaticMethodID获得main方法的ID
  • 然后使用CallStaticVoidMethod来调用Java层的函数

到这里,Zygote进程的初始化过程将转到Java层了,还记得app_processmain()函数中的启动逻辑么?

回顾一下哈:

    if (zygote) {
        // 如果是--zygote,则执行ZygoteInit类,启动Zygote进程
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        // 如果是指定了class name,则通过RuntimeInit执行传进来的类
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    }

接下来,我们就来看看ZygoteInit.java类吧

初始化工作-ZygoteInit

ZygoteInit类负责Zygote进程Java层的初始化工作。 入口方法main()的具体代码如下(注释比较详细):

    public static void main(String argv[]) {
        // 创建Zygote服务管理类,用来注册socket监听
        ZygoteServer zygoteServer = new ZygoteServer();
        // Mark zygote start. This ensures that thread creation will throw
        // an error.
        // 调用此方法后,虚拟机会拒绝线程的创建,如果此时创建会产生错误
        ZygoteHooks.startZygoteNoThreadCreation();
        // Zygote goes into its own process group.
        Os.setpgid(0, 0);
        //......
        final Runnable caller;
        try {
            ......省略打印相关
            // 启动DDMS虚拟机监控调试服务
            RuntimeInit.enableDdms();
            
            // 参数解析
            boolean startSystemServer = false;
            String socketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    // 开启系统服务
                    startSystemServer = true;
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    // 启动延迟加载
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    // abi类型,一个CPU对应一个abi
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    // 解析socket name
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    // 未知参数会抛出异常
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }
            // 没有指定ABI参数会抛出异常
            if (abiList == null) {
                throw new RuntimeException("No ABI list supplied.");
            }
            // 注册Zygote的socket监听端口,用来接收启动应用程序的消息
            zygoteServer.registerServerSocketFromEnv(socketName);
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                // 没有启用延迟加载的情况
                .....省略一些log打印
                // 执行预加载操作,包括系统预加载类、Framework资源和openGL资源
                preload(bootTimingsTraceLog);
                .....省略一些log打印
            } else {
                // 延迟加载的情况,reset 线程的优先级
                Zygote.resetNicePriority();
            }
            // Do an initial gc to clean up after startup
            ......省略log打印
            // 官方注释:运行几个指定的GC,尝试清除几代的软引用和可达的对象,还有别的垃圾
            // 此方法只在fork()前是好使的
            gcAndFinalize();
            ......省略log打印
            // 一些与安全相关的初始化操作
            Zygote.nativeSecurityInit();
            // Zygote process unmounts root storage spaces.
            // 目的是将整体的存储目录/storage卸载,取而代之的是挂载临时目录
            // 这个动作和Android 的沙箱(隔离存储)有关
            Zygote.nativeUnmountStorageOnInit();
            // 呼应前面的startZygoteNoThreadCreation()方法
            // 通知虚拟机,现在可以在zygote进程中创建线程了
            ZygoteHooks.stopZygoteNoThreadCreation();
            if (startSystemServer) {
                // fork SystemServer
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                // 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行
                if (r != null) {
                    // r 不为空,说明是孵化的子进程 systemserver,启动后直接返回
                    r.run();
                    return;
                }
            }
            Log.i(TAG, "Accepting command socket connections");
            // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            // 这部分是在zygote进程中执行
            // 此处进入一个无限循环中,处理zygote socket接收到数据
            caller = zygoteServer.runSelectLoop(abiList);
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            // 请留意这部分,主要是给子进程用的
            // 子进程中是不需要在有zygote服务的
            // 所以这里的关闭理论上是为了在子进程中关闭无用的zygote服务
            zygoteServer.closeServerSocket();
        }
        // We're in the child process and have exited the select loop. Proceed to execute the
        // command.
        // 除systemserver外的进程,都是在这里真正的开始main函数的
        // 有没有感到疑惑,runSelectLoop是一个无线循环,要怎么才能周到这里呢?
        // 走到这里为什么一定是子进程呢?
        if (caller != null) {
            caller.run();
        }
    }

看完这个main函数大家一定有很多疑问,由于有了子进程,代码的处理逻辑变得有些复杂

现将本人学习过程中的疑惑点和理解与大家分享下

知识点:fork的结果

关于fork,我们需要记住:

  • fork会产生和当前进程完全一样的新进程
  • fork函数调用后
    • 新的进程将启动
    • 并和当前进程一起从fork函数返回
  • 关于fork的返回值
    • 新的进程返回0
    • 当前进程返回新进程的pid

疑问2:systemserver的启动

            if (startSystemServer) {
                // fork SystemServer
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                // 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行
                if (r != null) {
                    // r 不为空,说明是孵化的子进程 systemserver,启动后直接返回
                    r.run();
                    return;
                }
            }

这段代码通过forkSystemServer来完成systemserverfork,而forkSystemServer逻辑是:

  • 执行pid = Zygote.forkSystemServer

    • 请注意,执行完后会产生两个进程:zygotesystemserver
    • 并且,两个进程同时开始执行,但是进程收到的pid是不一样的
    • 可参照前面的知识点
  • pid判断:

        // 对于systemserver进程,pid是0
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
            zygoteServer.closeServerSocket();
            // handleSystemServerProcess这个函数会:
            // 通过反射找到SystemServer的main函数
            // 并包装到Runnable的run函数中
            return handleSystemServerProcess(parsedArgs);
        }
        // pid不等于0,父进程,也就是zygote,返回null
        return null;
    

到这里,大家应该明白了下面这段代码的处理意义了吧:

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

疑问3:main函数最后的caller.run()怎么执行到的

应用程序的启动应该就是通过这个caller.run(),弄明白它问题就不大了

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

官方注释已经说了caller.run();才是子进程真正运行的地方,但是zygoteServer.runSelectLoop不是一个无线循环么,咋处理的呢?

我们来看下runSelectLoop函数:

    Runnable runSelectLoop(String abiList) {
        ......
        while (true) {
            ......
            try {
            ZygoteConnection connection = peers.get(i);
            final Runnable command = connection.processOneCommand(this);
            if (mIsForkChild) {
                // We're in the child. We should always have a command to run at this
                // stage if processOneCommand hasn't called "exec".
                if (command == null) {
                    throw new IllegalStateException("command == null");
                }
                return command;
            } else {
                // We're in the server - we should never have any commands to run.
            ......
            }
        }
        ......
    }

两个重要的点:

  • 一个mIsForkChild属性
    • mIsForkChild属性为true时,会退出循环
    • 并返回一个Runnable对象
  • 一个processOneCommand函数

对于processOneCommand函数,它也有一个很重要的操作:

Runnable processOneCommand(ZygoteServer zygoteServer) {
        ......
        // fork 子进程
        pid = Zygote.forkAndSpecialize(......;
        if (pid == 0) {
            // in child
            // 设置mIsForkChild为true
            zygoteServer.setForkChild();
            zygoteServer.closeServerSocket();
            ......
            // handleChildProc这个函数会:
            // 通过反射找到启动类的main函数
            // 并包装到Runnable的run函数中
            return handleChildProc(......);
        } else {
            ......
            return null;
        }
}

zygote的无限循环里,当监听到数据后:

  • 通过processOneCommand函数fork出了子进程,此时:
    • 存在两个进程正在运行:zygote子进程
  • 仍然还是根据pid来判断哪个是子进程
    • 对于子进程,设置mIsForkChild属性为true
    • 并且子进程将打包好的Runnable对象返回
    • 对于zygote,返回null

这样,子进程中的runSelectLoop()便会停止循环,执行到caller.run()

书中Android 5.0的源码是通过抛出异常的方式来执行caller.run(),也很有趣,哈哈哈

有了这些铺垫,相信下面zygote 启动应用程序这部分应该就不难了吧,哈哈哈

Zygote启动应用程序

我们已经知道,Zygote进程初始化完毕后,会通过runSelectLoop()化身为守护进程来执行启动应用程序的任务,先看下整个过程的时序图,再逐一详解吧

image

注册Zygote的socket

ZygoteInit类的main()函数

  • 首先调用了ZygoteServerregisterServerSocketFromEnv来创建一个本地的socket
  • 接着调用runSelectLoop来进入等来socket连接的循环中

registerServerSocketFromEnv()函数如下:

    void registerServerSocketFromEnv(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            // 拼接后的fullSocketName为ANDROID_SOCKET_zygote
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }
            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);
                mCloseSocketFd = true;
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

函数通过环境变量ANDROID_SOCKET_zygote来获取socketfileDesc

  • 这个fileDesc(文件描述符)怎么来的呢?我们再来回顾下init.rc中的内容:
    service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    ......
    socket zygote stream 660 root system
    ......
    
  • Zygote服务启动时指定了一个option用来进行socket的创建
    • Init进程会根据这条选项来创建一个用于本地通信的socket
    • 并把这个socketfileDesc放到环境变量ANDROID_SOCKET_zygote
    • 环境变量字符串后面部分的zygote就是option中的名称
  • 得到fileDesc后,通过LocalServerSocket()创建一个本地的socket服务,并保存到全局变量mServerSocket

请求启动应用

Android启动一个新的进程是在ActivityManagerService中完成的,可能会有很多原因导致系统启动一个新的进程,最终在ActivityManagerService中都是通过调用方法startProcess()来实现:

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

可以看到,关键的函数为Process.start()。而Process.start()的第一个参数entryPoint就是启动后执行的Java类:android.app.ActivityThread

我们这里主要是想看下启动应用时发送给Zygote的参数有哪些?而关于应用的启动流程,后面再进行展开哈

我们跟踪下Process.start():

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

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

参数有点多,代码部分意思一下哈,嘿嘿

  • start()方法调用startViaZygote()方法来启动应用
  • startViaZygote()首先将应用进程的启动参数保存到argsForZygote集合中
  • 然后调用zygoteSendArgsAndGetResult方法将应用程序进程的启动参数发送的Zygote进程
  • zygoteSendArgsAndGetResult()方法中会调用openZygoteSocketIfNeeded()方法用来创建用于通信的socket,整理后的代码是这样的:
    public static final String ZYGOTE_SOCKET = "zygote";
    public static final LocalSocketAddress address = new LocalSocketAddress(ZYGOTE_SOCKET, LocalSocketAddress.Namespace.RESERVED);
    final LocalSocket zygoteSocket = new LocalSocket();
    zygoteSocket.connect(address);
    
    • 对于Local Socket使用字符串作为地址就可以通信,灰常方便啊
  • socket连接建立完后,zygoteSendArgsAndGetResult利用这个socket进行启动参数的写入:
    writer.write(Integer.toString(args.size()));
    writer.newLine();
    for (int i = 0; i < sz; i++) {
        String arg = args.get(i);
        writer.write(arg);
        writer.newLine();
    }
    writer.flush();
    

到这里,请求应用启动的参数就发送给Zygote进程了,我们接下来看下Zygote的处理过程

处理启动应用的请求

Zygote进程通过runSelectLoop()来监听和处理启动应用的请求,我们看下runSelectLoop(),注释比较详细哈:

    Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
        // 将socket zygote的文件描述符添加到fds集合中
        // 此处需要留意,mServerSocket是集合中第一个元素
        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);
        while (true) {
            // 动态生成pollFds数组,作为poll的监听参数
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                // 通过poll对pollFds数组中的文件进行事件监听
                // -1 表示阻塞直到有数据进来
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            // 当有数据到来时,才会执行到这里
            for (int i = pollFds.length - 1; i >= 0; --i) {
                // 循环遍历,查找接收到数据的文件
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    // i=0 说明是 mServerSocket
                    // 意味着客户端发起了连接请求
                    // 按照LocalServerSocket的流程,需要创建一个LocalSocket来用于客户端的通信
                    // acceptCommandPeer()就是按照这个流程来创建ZygoteConnection对象
                    // 其实真正的通信对象还是LocalSocket
                    // 然后将通信对象的FD添加到fds集合中
                    // 大家没必要纠结这部分的处理方式,当做是LocalSocket的标准用法就可以啦
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    try {
                        // 这里才是真正的处理部分
                        ZygoteConnection connection = peers.get(i);
                        // 执行对应connection的processOneCommand函数
                        // 函数中会读取解析参数并进行子进程fork的相关操作
                        final Runnable command = connection.processOneCommand(this);

                        if (mIsForkChild) {
                            ......
                            // 子进程,退出selectLoop循环
                            return command;
                        } else {
                            ......
                            // zygote进程,关闭移除connection
                            if (connection.isClosedByPeer()) {
                                connection.closeSocket();
                                peers.remove(i);
                                fds.remove(i);
                            }
                        }
                    } catch (Exception e) {
                        if (!mIsForkChild) {
                            // 异常后的清除操作
                            ......
                            ZygoteConnection conn = peers.remove(i);
                            conn.closeSocket();
                            fds.remove(i);
                        } else {
                            ......
                        }
                    } finally {
                        ......
                        mIsForkChild = false;
                    }
                }
            }
        }
    }

从上面的代码中我们知道,对于消息真正的处理是在ZygoteConnection.processOneCommand函数中,我们来重点看下:

processOneCommand函数

函数分为了三部分:

  • 解析启动参数
  • 子进程fork
  • 子进程初始化

我们来逐一看下

解析启动参数

参数解析的流程是

  • 先通过readArgumentList方法从socket连接中读取多个参数行
    • 参数行的样式是--setuid=1
    • 行与行之间以\r\n或者\r\n分割
  • 读完后再调用Arguments类的parseArgs方法将数据解析为参数列表
    • 具体的参数意义,大家可以参考Arguments类的注释

解析完参数后,processOneCommand还会对解析出的参数进行检查和设置:

        ...... 省略一些参数属性判断
        // 检查客户端手有权限指定进程的用户ID、组ID
        // 如果是 root 进程,可以任意指定
        // 如果是 sys 进程,需要在ro.factorytest值 > 0时可以指定
        applyUidSecurityPolicy(parsedArgs, peer);
        // 判断是否具有invoke-with的执行权限
        applyInvokeWithSecurityPolicy(parsedArgs, peer);
        // 如果ro.debuggable是1的话,启动JDWP协议
        applyDebuggerSystemProperty(parsedArgs);
        // 处理invoke-with属性
        applyInvokeWithSystemProperty(parsedArgs);

细节就不展开了哈

fork子进程

参数检查无误后,processOneCommand函数会调用forkAndSpecialize方法来fork子进程:

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

forkAndSpecialize最终是通过native层的ForkAndSpecializeCommon来完成fork的,我们这里只是简单介绍下函数的主要工作哈:

  • 通过fork创建子进程
  • 在子进程中挂载emulate storage
  • 在子进程中设置用户ID、组ID、和进程所属的组
  • 在子进程中通过setrlimit系统调用设置进程的系统资源限制
  • 在子进程中通过capset系统调用设置进程的权限
  • 在子进程中通过selinux_android_setcontext设置应用进程的安全上下文

native层返回Java层

  • 如果pid==0,表示处于子进程中,执行handleChildProc函数
  • 如果pid!=0,表示在Zygote进程中,执行handleParentProc函数,并返回null

这部分代码在前面的疑问中其实已经展示过了哈:

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

子进程的初始化

Zygote进程fork出子进程后,调用handleChildProc方法来完成子进程的初始化工作。

  • handleChildProc方法首先关闭了监听用的socket和从Zygote中继承的文件描述符
        closeSocket();
        ......
        for (FileDescriptor fd: descriptors) {
            IoUtils.closeQuietly(fd);
        }
        ......
    
  • 接下来,根据启动参数是否有--runtime-init以及--invoke-with来判断如何初始化
        if (parsedArgs.invokeWith != null) {
            WrapperInit.execApplication(......);
            ......
        } else {
            ......
            return ZygoteInit.zygoteInit(......);
            ......
        }
    
    • 启动apk应用都会带有--runtime-init参数,但是--invoke-with通常为null
    • --invoke-with不为null将会通过exec的方式启动app_process来执行Java
    • 正常情况下会调用ZygoteInit.zygoteInit方法
  • ZygoteInit.zygoteInit方法又调用了三个方法:RuntimeInit.commonInit()ZygoteInit.nativeZygoteInit()RuntimeInit.applicationInit(),最后return一个Runnable的对象给调用者
    • commonInit():一些通用配置的简单初始化:
      • 设置KillApplicationHandler为默认的UncaughtExceptionHandler
      • 设置时区
      • 设置http.agent属性,用于HttpURLConnection
      • 重置AndroidLog系统
      • 通过NetworkManagementSocketTagger设置sockettag,用于流量统计
    • nativeZygoteInit()
      • 这是一个本地方法,但是很重要,因为它做了这么一件事
      static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
      {
          gCurRuntime->onZygoteInit();
      }
      
      • gCurRuntime?有木有很眼熟,它其实就是AppRuntime的实例,大家可以往上翻看AndroidRuntime初始化的部分
      • 然后调用了onZygoteInit,我们看下AppRuntime的函数实现:
      virtual void onZygoteInit()
      {
          sp<ProcessState> proc = ProcessState::self();
          proc->startThreadPool();
      }
      
      • 这段代码是不是也眼熟,哈哈哈,在Binder章节我们已经学过了
      • 主要是初始化Binder的使用环境,这样,应用进程就可以使用Binder
    • applicationInit():前两步初始化完成后,会执行并返回这个函数
      • 设置虚拟机的HeapUtilization0.75f
      • 设置当前的SDKVersion
      • 除了上面的两个,最重要的是调用了findStaticMain()函数来查找Java类的main方法,并包装成Runnable的形式
      protected static Runnable findStaticMain(......) {
          Class<?> cl = Class.forName(className, true, classLoader);
          Method m = cl.getMethod("main", new Class[] { String[].class });
          int modifiers = m.getModifiers();
          return new MethodAndArgsCaller(m, argv);
      }
      static class MethodAndArgsCaller implements Runnable {
          private final Method mMethod;
          private final String[] mArgs;
          ......
          public void run() {
              ......
              mMethod.invoke(null, new Object[] { mArgs });
              ......
          }
      }
      
      • 到这里,函数的调用就结束了
      • 这都是一些Java反射的相关知识啦,不清楚的话要学习哟

processOneCommand函数的终点

processOneCommand函数执行完会出现两种情况:

  • 对于子进程,返回都打包好的MethodAndArgsCaller实例
  • 对于Zygote,继续循环

此时,子进程的runSelectLoop会退出循环,并返回MethodAndArgsCaller实例,最终执行到:

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

预加载系统类和资源

为了加快应用程序的启动,Android会把系统常用的Java类和一部分Framework的资源放在Zygote进程中进行预加载。这些预加载的类和资源在所有经zygote进程fork出的子进程中是共享的。

如图所示:
image

预加载操作是在ZygoteInit.main()函数中,通过preload()函数来完成的,代码如下:

    static void preload(TimingsTraceLog bootTimingsTraceLog) {
        ......
        // 设置软引用保护,避免被GC回收掉
        beginIcuCachePinning();
        ......
        // 加载系统类
        preloadClasses();
        ......
        // 加载系统资源
        preloadResources();
        ......
        // 
        nativePreloadAppProcessHALs();
        ......
        // 加载OpenGL资源
        preloadOpenGL();
        // 加载一些共享so库,其实就三个:android、compiler_rt、jnigraphics
        preloadSharedLibraries();
        // 加载字体资源
        preloadTextResources();
        ......
        // 加载webview相关资源
        WebViewFactory.prepareWebViewInZygote();
        // 取消软引用保护
        endIcuCachePinning();
        // 初始化JCA安全相关的参数
        warmUpJcaProviders();
        Log.d(TAG, "end preload");
        sPreloadComplete = true;
    }

注释比较详细,我们重点看下Java类、系统资源和共享库的加载

预加载Java类

Android将所有需要预加载Java类放在文本文件preload-classes中。

9.0源码中的文件存放路径是在frameworks/base/config/preloaded-classes,内容格式如下:

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

preloadClasses()函数的作用就是解析这个文件,并加载里面声明的的所有类,函数如下:

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

到此,对于这些类,虚拟机也仅仅是完成了装载而已。只有在首次主动使用时,才会真正的进行类的初始化

preloaded-classes声明了这么多类,它是怎么生成的呢?

生成流程是:

  • 首先有一个frameworks/base/tools/preload/WritePreloadedClassFile.java类,它对应的modulepreload
  • 其次还有有一个frameworks/base/tools/preload/20100223.compiled文件
  • 然后通过mmm frameworks/base/tools/preload可以编译出preload.jar
  • 最后通过java -Xss4M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled来解析,解析的结果是得到一个包含所有Java类的列表,列表的内容包括:
    • root对象
    • 被几个进程装载
    • 装载的平均时长
  • 最后文件生成路径在/frameworks/base/preloaded-classes

20100223.compiled是源码自带的,那么问题就来了,.compiled这种类型的文件是怎么生成的呢?

  • 首先,有一个frameworks/base/tools/preload/Compile.java类,也是在preload中,它是用来分析logpreload信息的
  • 然后我们可以通过java -Xss512M -cp out/host/linux-x86/framework/preload.jar Compile logcat.txt 20201111.compiled来生成分析结果,也就是.compiled文件

预加载系统资源

预加载系统资源的函数是preloadResources(),主要代码如下:

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

预加载Framework的资源核心的两个函数是:

  • preloadDrawables:加载drawable资源
  • preloadColorStateLists:加载color状态定义资源

具体资源加载过程后面再详细学习,我们先看下预加载资源的定义,在源码目录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>

预加载共享库

预加载共享库的函数是preloadSharedLibraries(),代码如下:

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

一共是是三个so文件:

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

结语

Zygote进程的学习到这里就结束啦,又进一步加深了对Android的认知,哈哈哈哈!

为啥这里叒叒叒叒有结语呢?哈哈哈哈,好奇宝宝可以看这里!

一个无关紧要的私人计划

知识终归是知识,技巧还是有待磨练滴! 下一篇开始学习Android 的资源管理,加油~~