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

    xiaoxiao2023-03-24  4

    原文地址: http://blog.csdn.net/a34140974/article/details/50915307

    此博文为《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类):

    可以看到,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孵化进程的过程如下图所示。

    转载请注明原文地址: https://ju.6miu.com/read-1202233.html
    最新回复(0)