此博文为《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孵化进程的过程如下图所示。