Android6.0源码分析—— Zygote进程分析(补充)

时间:2022-07-07 16:49:28

此博文为《Android5.0源码分析—— Zygote进程分析》的补充

我们已经知道Android 5.0已经默认了ART,今天本想回去查看一下这个部分,于是回到init进程中去寻找源码,发现6.0的Zygote部分也小有变动,因此更新一下。

首先是init.c变成了init.cpp,这其实也就意味着在init中增加了类的概念。但是仔细查看init.h发现并没有class关键字。只有很多的struct。如果对C++比较了解,就应该知道C++的struct和C中的struct其实已经是不一样的概念了,在C++中struct除了公私与class对调外,其他的基本上没有区别。在init.h中定义了以下结构体:

struct service {

    void NotifyStateChange(const char* new_state);

 

        /* list of all services */

    struct listnode slist;

 

    char *name;

    const char *classname;

 

    unsigned flags;

    pid_t pid;

    time_t time_started;    /* time of last start */

    time_t time_crashed;    /* first crash within inspection window */

    int nr_crashed;         /* number of times crashed within window */

 

    uid_t uid;

    gid_t gid;

    gid_t supp_gids[NR_SVC_SUPP_GIDS];

    size_t nr_supp_gids;

 

    const char* seclabel;

 

    struct socketinfo *sockets;

    struct svcenvinfo *envvars;

 

    struct action onrestart;  /* Actions to execute on restart. */

 

    std::vector<std::string>* writepid_files_;

 

    /* keycodes for triggering this service via /dev/keychord */

    int *keycodes;

    int nkeycodes;

    int keychord_id;

 

    IoSchedClass ioprio_class;

    int ioprio_pri;

 

    int nargs;

    /* "MUST BE AT THE END OF THE STRUCT" */

    char *args[1];

};

可以看到,与之前5.0版本的最大区别就是结构体内多了一个函数(5.0也有类似功能的函数但是被放在结构体外)!从函数的起名来看这个函数应该是负责通知Service的状态变化。Android6.0作这样的改变我认为仅仅是为了封装。

另外一个就是init.main作了比较大的调整,但是同样这些调整也只是让整个程序的封装性和可读性更强罢了。实质的处理流程并没有什么变化。调整后的main函数如下:(变动比较大的部分已经用金底红字标出)

int main(int argc, char** argv) {

    if (!strcmp(basename(argv[0]), "ueventd")) {

        return ueventd_main(argc, argv);

    }

 

    if (!strcmp(basename(argv[0]), "watchdogd")) {

        return watchdogd_main(argc, argv);

    }

 

    // Clear the umask.

    umask(0);

 

    add_environment("PATH", _PATH_DEFPATH);

 

    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

 

    // Get the basic filesystem setup we need put together in the initramdisk

    // on / and then we'll let the rc file figure out the rest.

    if (is_first_stage) {

        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");

        mkdir("/dev/pts", 0755);

        mkdir("/dev/socket", 0755);

        mount("devpts", "/dev/pts", "devpts", 0, NULL);

        mount("proc", "/proc", "proc", 0, NULL);

        mount("sysfs", "/sys", "sysfs", 0, NULL);

    }

 

    // We must have some place other than / to create the device nodes for

    // kmsg and null, otherwise we won't be able to remount / read-only

    // later on. Now that tmpfs is mounted on /dev, we can actually talk

    // to the outside world.

    open_devnull_stdio();

    klog_init();

    klog_set_level(KLOG_NOTICE_LEVEL);

 

    NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");

 

    if (!is_first_stage) {

        // Indicate that booting is in progress to background fw loaders, etc.

        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

 

        property_init();

 

        // If arguments are passed both on the command line and in DT,

        // properties set in DT always have priority over the command-line ones.

        process_kernel_dt();

        process_kernel_cmdline();

 

        // Propogate the kernel variables to internal variables

        // used by init as well as the current required properties.

        export_kernel_boot_props();

    }

 

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.

    selinux_initialize(is_first_stage);

 

    // If we're in the kernel domain, re-exec init to transition to the init domain now

    // that the SELinux policy has been loaded.

    if (is_first_stage) {

        if (restorecon("/init") == -1) {

            ERROR("restorecon failed: %s\n", strerror(errno));

            security_failure();

        }

        char* path = argv[0];

        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };

        if (execv(path, args) == -1) {

            ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));

            security_failure();

        }

    }

 

    // These directories were necessarily created before initial policy load

    // and therefore need their security context restored to the proper value.

    // This must happen before /dev is populated by ueventd.

    INFO("Running restorecon...\n");

    restorecon("/dev");

    restorecon("/dev/socket");

    restorecon("/dev/__properties__");

    restorecon_recursive("/sys");

 

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);

    if (epoll_fd == -1) {

        ERROR("epoll_create1 failed: %s\n", strerror(errno));

        exit(1);

    }

 

    signal_handler_init();

 

    property_load_boot_defaults();

    start_property_service();

 

    init_parse_config_file("/init.rc");

 

    action_for_each_trigger("early-init", action_add_queue_tail);

 

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...

    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");

    // ... so that we can start queuing up actions that require stuff from /dev.

    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    queue_builtin_action(keychord_init_action, "keychord_init");

    queue_builtin_action(console_init_action, "console_init");

 

    // Trigger all the boot actions to get us started.

    action_for_each_trigger("init", action_add_queue_tail);

 

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random

    // wasn't ready immediately after wait_for_coldboot_done

    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

 

    // Don't mount filesystems or start core system services in charger mode.

    char bootmode[PROP_VALUE_MAX];

    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {

        action_for_each_trigger("charger", action_add_queue_tail);

    } else {

        action_for_each_trigger("late-init", action_add_queue_tail);

    }

 

    // Run all property triggers based on current state of the properties.

    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

 

    while (true) {

        if (!waiting_for_exec) {

            execute_one_command();

            restart_processes();

        }

 

        int timeout = -1;

        if (process_needs_restart) {

            timeout = (process_needs_restart - gettime()) * 1000;

            if (timeout < 0)

                timeout = 0;

        }

 

        if (!action_queue_empty() || cur_action) {

            timeout = 0;

        }

 

        bootchart_sample(&timeout);

 

        epoll_event ev;

        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));

        if (nr == -1) {

            ERROR("epoll_wait failed: %s\n", strerror(errno));

        } else if (nr == 1) {

            ((void (*)()) ev.data.ptr)();//其实最根本的改变在这,为了封装

        }

    }

 

    return 0;

}

总的来讲,6.0其实就是将原先5.0的方式改成了注册式的。使得结构性更强。

整理Zygote的启动大致如下图所示(带点红色的标示C++类,纯绿的为java类):

Android6.0源码分析—— Zygote进程分析(补充)

可以看到,ART和Dalvik的启动是在JniInvocation.init(NULL)中,init的参数可以指定使用哪个虚拟机,如果是NULL则默认ART。实际上可以认为是JniInvocation封装掉了两种虚拟机之间的差异(当然这仅仅是说的启动)。

另一个收获就是弄清楚了classname启动,是指一些非Zygote 的java 程序的启动路径,如am(shell),这种进程和Zygote孵化出来的进程最大的区别就是没有binder线程池。

//App_main.main()

if (zygote) {

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

    } else if (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.");

        return 10;

    }

再次总结Zygote孵化进程的过程如下图所示。

Android6.0源码分析—— Zygote进程分析(补充)