Zygote 是 Android 系统中一个重要的进程,应用程序进程与系统关键服务都来源于此,它也是连接 Linux Kernel 与 Java Framework 的桥梁。

本篇文字来闲扯一下 Zygote。

第一束光

zygote,/ˈzaɪ.ɡoʊt/,noun.。合子;受精卵。

正如其名,Zygote 是 Android 中应用层进程的根源。

  • Zygote 进程对 Dalvik/ART 进行初始化,提供了 Java 运行环境,是 Java Framework 的开端。

  • 应用程序所在进程与系统服务(比如 ActivityManagerService)进程均由 Zygote 进程 Fork 所得。因此,子进程可避免启动虚拟机或加载系统资源等耗时操作,但需要注意的是,避免启动虚拟机耗时的原因是子进程对已启动的虚拟机进行拷贝,而非共享虚拟机实例

在 UNIX/Linux 中,通过 fork() 函数对当前进程进行拷贝,新创建的进程与原进程共享同一代码空间,但拥有各自独立的数据空间(准确地说,由于 Linux 的 copy-on-write 机制,等到数据空间发生更改时,操作系统才为子进程分配内存空间,在此之前子进程引用的仍是原进程的内存空间,详细内容可查看 Linux Programmer’s Manual - fork)。

通过 fork() 函数所创建的进程如同原进程的一份克隆,创建完成后两个进程的代码执行所在的位置均为 fork() 函数所在的位置,区别在于,原进程中 fork() 的返回值为子进程的 Process ID,而子进程中返回 0(因为没有子进程)。

初始化

Zygote 进程是通过 Linux 所启动的 init 进程创建的,init 进程通过对系统根目录下的 init.rc 脚本文件进行解析,以启动操作系统所需的服务进程,Zygote 便是其中之一。

在 Android 源码中,该脚本文件的路径为 /system/core/rootdir/init.rc

在 Android Oreo 之后,init.rc 进行了拆分,每一个服务对应一个文件。init.rc 通过 import 命令将其它服务的启动脚本文件导入。

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#

import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc

...

init.rc 等文件采用 Android Init Language 进行编写。

需要注意的是,这些文件仅仅是脚本文件,并不是程序,其运行是由 init 进程对其进行解析后执行指定的内容。

init 进程的入口函数 main() 位于 /system/core/init/init.cpp 中,语言相关的命令解析器也均位于 /system/core/init

init.rc 中,import 命令通过属性 ro.zygote 导入对应版本的 Zygote 启动脚本。

属性机制类似于 Windows 操作系统中的注册表,通过键值对的形式存储信息。存储与管理属性的属性服务同样也是在 init 进程中被初始化,且要早于 Zygote 进程。

下方为 init.zygote64.rc 的内容。

service zygote /system/bin/app_process64 -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/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

先看一下 service 命令的语法

# <name>        服务名称
# <pathname>    执行程序所在位置
# <argument>    参数
# <option>      修饰符,它将影响服务何时、如何运行
service <name> <pathname> [ <argument> ]*
   <option>
   ...

通过语法可得:

  • 服务名称为 zygote。
  • 运行程序所在位置为 /system/bin/app_process64
  • 程序参数包含 --zygote--start-system-server
  • 服务的 classname 为 main
  • 进程所执行的权限为 root。
  • 创建名为 /dev/socket/zygote 的 Unix domain socket,并将其文件描述符传递到启动的服务中。
  • 当 Zygote 重启时重启一些进程相关服务。

大部分内容根据 options 所得,受限于篇幅,文字中不再对 options 的相关语法进行说明,详细内容可通过 Android Init Language 进行了解。

/system/bin/app_process64 的源码文件为 /frameworks/base/cmds/app_process/app_main.cpp,下面是该源码文件的部分内容。

class AppRuntime : public AndroidRuntime
{
    ...
}

#if defined(__LP64__)
...
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else
...
static const char ZYGOTE_NICE_NAME[] = "zygote";
#endif

int main(int argc, char* const argv[])
{
    ...

    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));

    ...

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;

    ...

    ++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;
        } 
        ...
    }

    ...

    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    }
    ...
}

可见,在经历了命令参数的解析之后,程序最终调用了 AppRuntime 实例的 start() 函数。很明显,com.android.internal.os.ZygoteInit 是一个 Java 类的全限定名,可猜测 start() 函数的调用是运行该类的代码。但到目前为止,Zygote 并未建立 Java 运行环境,因此创建启动虚拟机的工作可预计在 start() 中进行。

start() 函数的实现位于 AppRuntime 的超类 AndroidRuntime 中,内容如下:

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
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;
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);

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

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

    /*
     * 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
        }
    }
    ...
}

在上述代码中,startVm() 创建了虚拟机,并通过 startReg() 注册 Android 系统库的 Jni 方法。在将参数转换为 Java 中的 String 类型之后,程序调用了 com.android.internal.os.ZygoteInitmain() 方法。至此,Zygote 进程,以至 Android 操作系统,正式进入 Java 运行环境。

在代码中使用到了 toSlashClassName() 函数,目的是为了将 Java 源代码形式的全限定名(如 moe.aoramd.Clazz)转换为 Class 文件的全限定名(如 moe/aoramd/Clazz),具体内容可见《浅尝第一杯咖啡》

走进咖啡厅

Zygote 相关的代码一直在不断重构,但主要功能的变动不大,因此与其上来就代码糊脸,不如先说一说 ZygoteInit.main() 进行了什么操作:

  • 创建 Unix domain socket 的 Java 实例,用于进程间通信。
  • 对 Android 的代码与资源进行预加载。
  • 启动 SystemServer 进程。
  • 等待 ActivityManagerService 请求创建应用程序进程。

下面便是 ZygoteInit.main() 的源码。

public class ZygoteInit {

    ...

    /**
     * This is the entry point for a Zygote process.  It creates the Zygote server, loads resources,
     * and handles other tasks related to preparing the process for forking into applications.
     *
     * This process is started with a nice value of -20 (highest priority).  All paths that flow
     * into new processes are required to either set the priority to the default value or terminate
     * before executing any non-system code.  The native side of this occurs in SpecializeCommon,
     * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess,
     * ZygoteConnection.handleChildProc, and Zygote.usapMain.
     *
     * @param argv  Command line arguments used to specify the Zygote's configuration.
     */
    @UnsupportedAppUsage
    public static void main(String argv[]) {
        
        ...

        Runnable caller;
        try {

            ...

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

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

            ...

            zygoteServer = new ZygoteServer(isPrimaryZygote);

            if (startSystemServer) {
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);

                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                if (r != null) {
                    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.
            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();
        }
    }

    ...
}

进程通过 preload() 方法对 Android 代码与资源进行了预加载,zygoteServer.runSelectLoop() 等待应用程序创建请求,这两者在此不作展开,我们来看看其它两个操作是如何完成的。

额外扯上一点。

在 Android 8.0 及之前,为了清理设置过程中的调用栈,caller 采用的是继承自 Exception,抛出后在 ZygoteInit.main() 捕获执行的设计,操作很秀,学到许多。但在 Android 8.1 之后,这种设计被弃用了,取之而代的是采用函数返回的形式回到 ZygoteInit.main() 中运行,以方便错误的排查与进程状态的检测。

bf99d06   [email protected]   2017-07-06 19:49 +08:00

Zygote: Improve logging and error handling during connections.

Before this change, we were throwing a checked exception on success
and returning on failure. This made it hard to figure out where / when
something was going wrong. This change switches things around to throw
a RuntimeException when something goes wrong and to return a Runnable
on success. This lets us make stronger assertions in both the parent
and the child process about their state and expected return values.

...

创建 Socket

Zygote 进程与 SystemServer 进程采用 Unix domain socket 进行通信。

Unix domain socket 又称为 IPC socket,用于在同一主机上进行跨进程通信。与 socket 相比,因为 IPC 本质是可靠的,因此不需要经过网络协议栈、校验、封包解包等操作,具有更高的效率。

Zygote 进程服务端 Socket 对象的创建过程位于 ZygoteServer 的构造器中,即在这一行代码中被创建。

zygoteServer = new ZygoteServer(isPrimaryZygote);

构造器实现如下:

/**
 * Server socket class for zygote processes.
 *
 * Provides functions to wait for commands on a UNIX domain socket, and fork
 * off child processes that inherit the initial state of the VM.%
 *
 * Please see {@link ZygoteArguments} for documentation on the
 * client protocol.
 */
class ZygoteServer {

    ...

    /**
     * Initialize the Zygote server with the Zygote server socket, USAP pool server socket, and USAP
     * pool event FD.
     *
     * @param isPrimaryZygote  If this is the primary Zygote or not.
     */
    ZygoteServer(boolean isPrimaryZygote) {
        ...
        if (isPrimaryZygote) {
            mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
            ...
        }
        ...
    }

    ...
}

可见 Socket 对象的创建过程位于 Zygote.createManagedSocketFromInitSocket() 中。

/** @hide */
public final class Zygote {

    ...

    /** Prefix prepended to socket names created by init */
    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";

    ...

    /**
     * Creates a managed LocalServerSocket object using a file descriptor
     * created by an init.rc script.  The init scripts that specify the
     * sockets name can be found in system/core/rootdir.  The socket is bound
     * to the file system in the /dev/sockets/ directory, and the file
     * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
     * variable.
     */
    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);
        }
    }

    ...
}

这一部分代码,通过字符串拼接的形式获得 Socket 全名 “ANDROID_SOCKET_zygote”,之后根据环境变量创建文件描述符 fd,最终创建 LocalServerSocket 实例。

Zygote 在启动 SystemServer 之后,便会通过该 Socket 等待 ActivityManagerService 的应用程序进程创建请求。

启动 SystemService

启动操作对应 ZygoteInit.main() 中的以下代码段:

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

转到 ZygoteInit.forkSystemServer()

/**
 * Prepare the arguments and forks for the system server process.
 *
 * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
 * process; {@code null} in the parent.
 */
private static Runnable forkSystemServer(String abiList, String socketName,
        ZygoteServer zygoteServer) {
    
    ...

    /* 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,3011",
            "--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);

        ...

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

    /* For child process */
    if (pid == 0) {
        ...
        zygoteServer.closeServerSocket();
        return handleSystemServerProcess(parsedArgs);
    }

    return null;
}

在设置完参数后,ZygoteInit.forkSystemServer() 调用了 Zygote.forkSystemServer() 以创建 SystemServer 进程。需要注意的点在于:

  • SystemServer 进程的用户 id 与用户组 id 被设置为 1000。

  • 启动类的类名为 com.android.server.SystemServer

  • Fork 所得的 SystemServer 关闭了服务端 Socket。

下面是 Zygote.forkSystemServer() 的实现。

public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
        int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
    ...

    int pid = nativeForkSystemServer(
            uid, gid, gids, runtimeFlags, rlimits,
            permittedCapabilities, effectiveCapabilities);

    // Set the Java Language thread priority to the default value for new apps.
    Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

    ...
    return pid;
}

nativeForkSystemServer() 方法的调用进入到 native 层,Zygote 进程最终在 ForkCommon() 调用 fork() 函数,完成 SystemServer 进程创建。

参考链接