系统启动过程 | Zygote 启动流程分析

203 阅读9分钟

在Android系统中,普通应用程序进程以及运行系统的服务system_server进程都是有Zygote进程fork来的,所以也叫Zygote孵化器。它是通过linux的fork形式创建应用程序进程和system_server进程。由于zygote进程在启动的时候会创建java虚拟机环境,因此通过fork而创将的应用程序或者system_server都可以在内部获得java虚拟机环境。

1 Zygote的启动

在系统开机启动有内核启动的第一个进程就是init进程,init进程会通过init.rc 包括import导入的各个模块的rc,包括前面几篇文章讲解的 bootanimation.rc surfaceflinger.rc等,zygote就是这样启动的,init.rc中就有导入:

import /init.${ro.zygote}.rc

路径在:system/core/rootdir/init.zygote32.rc 等,目录下有多个zygote.rc 是由于android系统支持64位系统和32位系统的原因

我们直接看init.zygote32.rc即可:

service zygote 表示要启动的服务名zygote,那么服务从哪里启动呢?就是通过/system/bin/app_process启动,后面传递的就是参数:-Xzygote /system/bin --zygote --start-system-server

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

那么/system/bin/app_process 是一个二进制的可执行程序,是通过系统编译出来的,那么源码在哪里呢?通过grep 命令查找app_process 直接学习过Android.mk 或 Android.bp 定义的模块名就是生成可执行程序的文件名,通过Android.mk或者Android.bp 就可以定义源码的位置

cd frameworks
grep "app_process" ./ -rn

查找的位置如下图所示:可以看到Android.mk中的LOCAL_MODULE定义的名字那么源码路径就在:

frameworks/base/cmds/app_process

就一个app_main.cpp文件,直接看main函数

main 函数里面代码非常多,只看重点即可:其实main函数主要解析了传递的参数,根据参数的信息调用了AppRunTime的方法:

int main(int argc, char* const argv[])
{
  ....
  AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;
  ....
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;
     while (i < argc) {
        const char* arg = argv[i++];//这里根据参数 对上述的变量进行赋值 --zygote --start-system-server
        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) {//这里在启动zygote的时候没有这个参数
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {//这里在启动zygote的时候没有这个参数
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {//这里在启动zygote的时候没有这个参数
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
     Vector<String8> args;//args作为参数 传递给runTime了
    if (!className.isEmpty()) {//className是空的 走else逻辑
    .......
    }else {
        ......
       if (startSystemServer) {
            args.add(String8("start-system-server"));
        }
        ......
    }
    if (!niceName.isEmpty()) {//设置线程名 当zygote为true的时候niceName = ZYGOTE_NICE_NAME
        //static const char ZYGOTE_NICE_NAME[] = "zygote";
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }
    ......
    if (zygote) {//zygote为true 走的这里的逻辑
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {//className是空的
        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.");
    }
}

下面主要看AppRunTime类,setArgv0start函数,如下代码所示:

这里会发现AppRunTime继承了AndroidRuntime,而这两个函数正是AndroidRuntime提供的

class AppRuntime : public AndroidRuntime

frameworks/base/core/jni/AndroidRuntime.cpp (放到了jni目录下面,可以猜测C++和Java通信?后面去验证猜想)

先看下setArgv0这个函数:代码很简单用到了pthread linux的线程,线程的使用可以看这篇文章Android Native层Thread 原理,setProcName = zygote 设置了以下线程名

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

再看start这个函数,通过Jni创建了Java虚拟机,并且调用到了java层:

  • className: com.android.internal.os.ZygoteInit
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
  ......
   /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;//C++和Java互相通信
    if (startVm(&mJavaVM, &env, zygote) != 0) {//JVM 启动了一个Java虚拟机
        return;
    }
    onVmCreated(env);//jvm 创建完毕的回调方法
     /*
     * Register android functions. 注册Android的一些方法
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    ....
    //toSlashClassName 把. 替换成 /  -> com/android/internal/os/ZygoteInit
     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 {//class != null
        //判断是否存在main方法,如果存在则调用
        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 {
            //这里就调到了java层了
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
            ......
        }
    }
    ......
}

同时我们也可以在AndroidRuntime中找到注册JNI:

extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
  • Init.rc 启动zygote
  • zygote 创建AppRuntime实例
  • 解析参数, —zygote 并执行setArgv0()设置线程名为:zygote
  • 执行runtime.start()方法
  • 初始化jni并创建和启动jvm-java虚拟机
  • 通过传递进来的classname,反射获取到class,并调用java层的main方法,此时进入到Java层

2 zygote Java层的启动

通过上述的分析,最终会调用到ZygoteInit.main()方法

          // Mark zygote start. This ensures that thread creation will throw
        // an error. 这个时候不允许线程创建的,如果有线程创建则抛出异常
        ZygoteHooks.startZygoteNoThreadCreation();
          ......
           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])) {
                    startSystemServer = true;//这里会置为true
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }

上述运行完后:

  • startSystemServer = true
  • enableLazyPreload = false

ZygoteHooks.startZygoteNoThreadCreation(); 这里有个疑问,按照注释描述是如果创建线程就会抛出异常,这是为什么呢?这里和后面的初始化系统级别的类和资源有关系

  • Zygote 阶段:这是系统的初始化阶段,Zygote 会通过加载常用的系统类库和资源来加速后续的应用启动。这时为了避免 fork() 时的多线程问题,限制了线程的创建。
  • 应用进程阶段:一旦 fork() 生成新的子进程,应用进程就进入了自己的运行阶段。在这个阶段,应用可以自由创建和管理自己的线程,比如 AsyncTaskHandlerThreadThreadPoolExecutor 等,这些都是 Android 中常见的多线程操作。

Zygote 是 Android 中非常重要的进程,负责创建和初始化 Java 虚拟机 (JVM) 以及系统的基本服务。它是所有应用进程的父进程,采用的是“forking”机制来启动新的应用进程。当系统需要启动一个新的应用时,Zygote 进程会通过 fork() 复制自身来生成新的子进程。通过这种方式,新进程可以继承父进程的资源和状态,比如类加载器、JVM 状态等。

在 Unix 系统中,fork() 是通过复制进程的内存空间来生成子进程的。在单线程环境下,fork() 是相对简单和安全的,因为它只需要复制当前线程的上下文。但如果在多线程环境下执行 fork(),则可能会出现一些问题。具体来说:

  • 线程状态不一致fork() 只会复制调用它的线程,而其他线程不会被复制。如果 Zygote 进程在 fork() 时已经创建了多个线程,那么在子进程中只有调用 fork() 的线程会被保留,其他线程会丢失。这样可能会导致不一致的状态,甚至资源泄漏或死锁。
  • 锁和资源的继承问题:多线程程序中,线程之间会共享很多资源(比如锁、文件描述符等)。如果在 fork() 时多个线程正在竞争某些共享资源,子进程可能会继承一些不一致的锁状态,导致死锁或其他未定义的行为。

因此,在 Zygote 进程中,**特别是在它准备 **fork() 子进程的阶段,**禁止创建新的线程是为了避免这些多线程下 **fork() 的问题。

这里禁止创建线程会在zygote进程初始化和fork()之前是要禁止创建的,通常情况下,startZygoteNoThreadCreation() 被调用是在 Zygote 进程初始化和 fork() 之前。Zygote 进程在初始化时会加载系统级别的类、资源等。这些操作完成后,才会调用 fork() 来创建应用进程。在 fork() 之前限制线程的创建,可以避免多线程下的 fork() 问题。

我们来看后续代码:

从如下代码中可以看到调用了stopZygoteNoThreadCreation 也就是说zygote进程初始化完成了,这里就放开了禁止创建新的线程,enableLazyPreload = false 所以会执行preload() 这个方法是关键这里会初始化夹杂系统级别的类和资源等,也就是说当preload执行完毕了就可以创建新线程了,避免了多个线程竞争某些资源

           final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);
            ......
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                .......
                preload(bootTimingsTraceLog);//预加载一些资源
                ......
            } else {
                Zygote.resetNicePriority();
            }
            ......
            Zygote.initNativeState(isPrimaryZygote);

            ZygoteHooks.stopZygoteNoThreadCreation();//zygote创建完了 停止这个阻止线程创建的方法

            zygoteServer = new ZygoteServer(isPrimaryZygote);

可以看下preload方法如何预加载的:

static void preload(TimingsTraceLog bootTimingsTraceLog) {
       
        beginPreload();
       
        preloadClasses();//预加载class
     
        preloadResources();//预加载资源文件
       
        nativePreloadAppProcessHALs();
        
        maybePreloadGraphicsDriver();
       
        preloadSharedLibraries();
        preloadTextResources();
  
        WebViewFactory.prepareWebViewInZygote();
        endPreload();
        warmUpJcaProviders();
       ......
        sPreloadComplete = true;
    }

这里只看下preloadClasses()如何预加载class的:

可以看到通过inputStream加载文件然后读取文件的内容,按照行读取然后调用Class.forName预加载class

 private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();

        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
          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 {
                    Class.forName(line, true, null);
                    }
                    .......
 }

private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";源码目录在:frameworks/base/config/preloaded-classes 可以看到这里面的内容都是系统级别的类

继续看ZygoteInit.main函数后面的代码:

  1. 创建ZygoteServer 这里创建socket,使用socket进行进程间的通信
  2. startSystemServer = true 这里就会fork SystemServer进程
  3. zygoteServer.runSelectLoop 会开启while(true)的循环,接收消息

Zygote 通信是非常简单的“请求-响应”模式:Zygote 等待启动请求,收到请求后 fork() 子进程,然后结束通信。因此,socket 通信能够有效支持这种一次性请求场景,不需要像 Binder 那样复杂的连接管理。Binder 复杂性和开销:相比之下,Binder 提供了更多的功能,如跨进程的直接方法调用、权限验证等,但这些功能在 Zygote 场景中并不需要,反而会增加系统的复杂性和性能开销,fork()**** 与多线程冲突:Binder 的多线程特性会在 Zygote 的 fork() 操作中引入复杂性,可能导致线程状态不一致等问题

           zygoteServer = new ZygoteServer(isPrimaryZygote);

            if (startSystemServer) {
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
                if (r != null) {
                    r.run();
                    return;
                }
            }

          

            // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            caller = zygoteServer.runSelectLoop(abiList);

总上所述,整体的zygote启动流程算是结束了 init → zygote → jvm 虚拟机 → java层 → ZygoteInit → preload → zygotesever → forkSystemServer → runSelectLoop