前言
我们之前在 Android 系统启动流程简析 中提到:Android 系统内核启动之后,除了初始化各种软硬件环境,加载驱动程序,挂载根文件系统等操作之外,最重要的一个操作就是在系统中找到可执行程序 init ,启动 init 进程。
下面我们对 init 进程启动源码进行简析。但在简析之前,需要我们预先了解一些前置知识。
前置知识
本文 Android 源码版本:android-6.0.1_r81
关键路径:如下表所示:
File | Path |
---|---|
init 进程 | /system/core/init/init.cpp |
init.rc 脚本 | /system/core/rootdir/init.rc |
readme.txt(Android Init Language) | /system/core/init/readme.txt |
- init.rc 文件格式:init 进程在启动过程中,会启动许多关键的系统服务,如果在源码中一项项启动会显得很繁琐,而且不易扩展,因此 Android 引入了 init.rc。init.rc 是一个配置脚本,其内部使用一种名为 Android Init Language 的语言编写而成,详细内容可在
/system/core/init/readme.txt
中查看。
这里我们就简单介绍下 init.rc 的一些主要配置语法:
☛ init.rc 内部主要包含五种类型语句:Action、Service、Command、Options 和 Import。其中,主要的配置选项为:Action 和 Service,Trigger 和 Command 是对 Action 的补充,Options 是对 Service 的补充。Import 用于导入其他 .rc 文件并进行解析。下面简单介绍下 Action 和 Service 的配置语法:
Action:以 on
开头,trigger
是判断条件,command
是具体执行一些操作,当满足 trigger
条件时,执行这些 command
。
on [&& ]* //设置触发器
//触发器触发之后要完成的操作
Service:服务是 init 进程启动的程序、当服务退出时 init 进程会视情况重启服务。其语法以 service
开头,name
是指定这个服务的名称,pathname
表示这个服务的执行文件路径,argument
表示传递给执行文件的参数,option
表示这个服务的一些配置。
service [ ]*
☛ .rc 文件是由一个个 Section 组成。action 加上 trigger 以及一些 command 组成一个Section;service 加上一些 option,也组成一个Section ;
注:在 Android 7.0 以前,init 进程只解析根目录下的 init.rc 文件,但是随着版本的迭代,init.rc 越来越臃肿,所以在 7.0 以后,init.rc 一些业务被分拆到 /system/etc/init,/vendor/etc/init,/odm/etc/init 三个目录下。
init 进程启动流程 简析
init 进程的入口函数为:main
,其位于:/system/core/init/init.cpp,具体内容如下:
int main(int argc, char** argv) {
// 如果不是 ueventd,则进入
if (!strcmp(basename(argv[0]), "ueventd")) {
/* 该函数定义在在system/core/init/ueventd.cpp
* init 进程会创建子进程 ueventd,uevented 主要负责设备节点文件的创建,
* 创建方式有两种:冷插拔(Cold Plug) 和 热插拔(Hot Plug)
*/
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
/* 该函数定义在/system/core/init/watchdogd.cpp,主要用于启动“看门狗”
*
* “看门狗”本身是一个定时器电路,内部会不断的进行计时(或计数)操作,
* 计算机系统和”看门狗”有两个引脚相连接,正常运行时每隔一段时间就会
* 通过其中一个引脚向”看门狗”发送信号,”看门狗”接收到信号后会将计时器
* 清零并重新开始计时,而一旦系统出现问题,进入死循环或任何阻塞状态,
* 不能及时发送信号让”看门狗”的计时器清零,当计时结束时,
* ”看门狗”就会通过另一个引脚向系统发送“复位信号”,让系统重启。
*/
return watchdogd_main(argc, argv);
}
// Clear the umask.
umask(0);
// 注册环境变量PATH
// _PATH_DEFPATH 定义在 bionic/libc/include/paths.h 中
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) {
// 挂载 tmpfs 到 /dev 目录
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
// 创建文件夹 /dev/pts
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();
// 初始化内核log,位于节点/dev/kmsg
klog_init();
// 设置 log 级别
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));
// 定义在 /system/core/init/property_service.cpp
// 初始化属性服务
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(); // 处理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 安全策略
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("--second-stage"), nullptr };
// 调用 execv 重新执行 main 函数,进入 second-stage 阶段
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();
// 加载default.prop文件
property_load_boot_defaults();
// 启动属性服务,开启一个socket监听系统属性的设置
start_property_service();
// 解析 init.rc
init_parse_config_file("/init.rc");
// 执行rc文件中触发器为 on early-init 的语句
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");
// 屏幕显示 Android 静态 Logo
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
// 触发rc文件中触发器为on init的语句
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];
// 当处于充电模式时,将 charger 加入执行队列;否则将 late-init 加入队列
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;
}
从源码中看到,init 进程分为两个阶段:first-stage 和 second-stage。
第一个阶段 first-stage 主要做的事为:挂载一些必要的文件系统:tmpfs,devpts,proc,sysfs等,然后初始化 selinux 安全策略,在 selinux 初始化完成后就立即重新执行main
函数,进入第二阶段 second-stage,也就是将 init 进程从内核态转化为用户态执行。
first-stage 和 second-stage 都会做一些同样的工作,比如设置环境变量,设置日志,设置不同阶段的 selinux 策略。对于 second-stage 来说,其主要做的就是:初始化和启动属性服务,解析 init.rc 文件,并按顺序先后执行触发器: on early-init -> init -> late-init。
属性服务 的作用是让 init 进程可以对其他程序进行权限控制。在 Android 中,对属性的设置都统一交由 init 进程管理,其他进程不能直接修改属性,而必须通过属性服务申请。其实属性服务的是一个跨进程通讯,使用的通讯机制是 socket。这里我们就不深入代码讲解了,感兴趣的请自行查看。
对于 init.rc 文件的具体解析过程,我们这里也不做讲解。但简单提一下,init_parse_config_file("/init.rc");
该函数源码位于:/system/core/init/init_parser.cpp ,主要做的就是对 init.rc 的文件内容进行解析,将解析出的 services 和 actions 等添加到运行队列中,等待 trigger 触发器的触发运行。
我们重点讲下 Zygote 的启动点。
启动 Zygote 进程
我们知道,Zygote 的启动是在 init 进程解析 init.rc 后启动的,那我们先来看下 init.rc 文件的内容:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
···
这里看到 Zygote 的启动脚本被 import 了进来:import /init.${ro.zygote}.rc
, ${ro.zygote} 会被替换成 ro.zyogte 的属性值,这个是由不同的硬件厂商自己定制的, 有四个值,zygote32、zygote64、zygote32_64、zygote64_32 ,也就是说可能有四种 .rc 文件。
这里我们选用 64 位处理器的版本来分析:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
service 选项用于通知 init 进程启动 zygote 程序,zygote 的执行程序路径为:/system/bin/app_process64,init 进程启动 zygote 时,传递的参数为: -Xzygote /system/bin --zygote --start-system-server。
class 的作用是用于批量管理 service。因此这里class main
表示 zygote 服务属于类main
。后续启动 main
的时候,就会启动 zygote 服务。
前面我们说过,init 进程在解析完 init.rc 文件后,会依次执行触发器:on early-init -> init -> late-init。我们再来看下 init.rc 文件内容:
on late-init
···
on nonencrypted
class_start main // zygote 属于类 main
class_start late_start
···
也就是说在 late-init 触发时,就会启动类 main
管理的服务,也就是会启动 zygote。
class_start
对应的处理方法是 do_class_start
,该方法定义在system\core\init\builtins.cpp 中:
int do_class_start(int nargs, char **args){
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
该方法内部又会调用 service_for_each_class
:
/system/core/init/init_parser.cpp
void service_for_each_class(const char *classname,
void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (!strcmp(svc->classname, classname)) {
func(svc);
}
}
}
该方法遍历 service_list
链表,每找到一个 classname
为 main
的 service,就调用 service_start_if_not_disabled
来进一步处理,service_start_if_not_disabled
方法源码如下:
/system/core/init/builtins.cpp
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
} else {
svc->flags |= SVC_DISABLED_START;
}
}
可以看到,最终是调用 service_start
来启动服务。service_start
源码如下:
/system/core/init/init.cpp
void service_start(struct service *svc, const char *dynamic_args)
{
···
// 如果Service已经运行,则不启动
if (svc->flags & SVC_RUNNING) {
return;
}
···
struct stat s;
// 判断Service要启动的执行文件是否存在
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
···
NOTICE("Starting service '%s'...\n", svc->name);
// fork 函数创建子进程
pid_t pid = fork();
if (pid == 0) { // 运行在子进程当中
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
···
if (!dynamic_args) {
// 加载可执行文件
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
···
_exit(127);
}
···
}
该函数会通过调用 fork
函数来创建子进程,并在子进程中调用 execve
来加载可执行文件,对于 zygote 服务来说,也就是会执行 /system/bin/app_process64。那接下来该可执行文件的 main
函数就会被调用:
framework/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
···
// Process command line arguments
// ignore argv[0]
argc--;
argv++;
// --zygote : Start in zygote mode
// --start-system-server : Start the system server.
// --application : Start in application (stand alone, non zygote) mode.
// --nice-name : The nice name for this process.
···
// Parse runtime arguments. Stop at first unrecognized option.
···
// 解析参数
++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;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
···
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;
}
}
该函数会先对参数进行解析,对照 init.zygote64.rc 传递的参数:-Xzygote /system/bin --zygote --start-system-server
,可以解析得出zygote=true,startSystemServer =true
。于是最终会通过 AppRuntime.start
方法启动 zygote。
到此,init 进程就成功启动了 zygote 进程。
总结
init 进程启动时,总共经历两个阶段:内核态 和 用户态。
内核态 阶段下,init 进程主要做挂载一些必要的设备节点(tmpfs,devpts,proc,sysfs····),初始化 selinux 安全策略等操作,然后切换到 用户态(通过传递参数 --second-stage
,并调用 execv
进行重启切换);
用户态 阶段下,init 进程最主要的一个操作就是解析 init.rc 文件,并按照顺序先后执行触发器:on early-init -> init -> late-init。
init 进程每遇到一个服务(service)时,就会通过 fork
出一个子进程来启动该服务。
zygote 在 init.rc 中是作为一个服务(service)存在,当 init.rc 文件被解析完成后,init 进程最终会在执行触发器 late-init 的过程中启动 zygote 服务(zygote 服务进程就是 init 进程 fork
出来的一个子进程,在该进程最后,会通过 AppRuntime.start
真正启动 zygote)。
参考
Android6.0系统启动流程分析一:init进程
Android6.0系统启动流程分析二:zygote进程
Android 8.0 系统启动流程之init进程--第一阶段(四)
Android 8.0 系统启动流程之init.rc语法规则(六)
Android系统启动-Init篇