init进程是Linux系统中用户空间的第一个进程,进程号为1。Kernel启动后,在用户空间启动init进程,并调用/system/core/init.cpp中的main方法执行一些重要的工作。
备注:本文将结合Android8.0的源码看init进程的启动过程以及init进程做了哪些重要工作。
1. init进程启动前系统的启动流程
在引入init进程前,我们需要大致了解系统是如何走到init进程的。大致步骤如下:
-
启动电源和系统启动
按下电源,让设备开机时引导芯片代码会从预定义的地方开始执行。加载引导程序BootLoader到RAM中。
-
BootLoader
BootLoader只是Android系统开始前的一个引导程序,主要作用是将OS系统拉起来并运行。
-
启动Linux内核
当内核启动时,会先去设置缓存、加载驱动等,在系统设置完成后,会在系统文件中寻找init.rc文件,并启动init进程。
-
启动init进程
经过前面的步骤,到这一步,init就被正式启动。
init进程启动后,下面就看下它里面的一些重要的职责工作。
2. init进程main函数
main函数是init进程的入口函数,代码位置在:
/system/core/init.cpp
深入到init.cpp的main函数中:
int main(int argc, char** argv) {
...省略...
if (is_first_stage) {
boot_clock::time_point start_time = boot_clock::now();
// Clear the umask.
umask(0);
//创建和挂载启动所需的文件目录
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
//初始化Kernel的Log
InitKernelLogging(argv);
...省略...
}
...省略...
//创建一块共享的内存空间,用于对属性服务进行初始化
property_init();
...省略...
//创建epoll句柄
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
//初始化子进程退出的信号处理函数
//如果子进程异常退出,init进程会调用该函数中设定的信号处理函数来进程处理
//防止子进程成为僵尸进程
signal_handler_init();
//加载default.prop文件,导入默认的环境变量
property_load_boot_defaults();
export_oem_lock_status();
//启动属性服务器,会调用epoll_ctl设置property fd可读的回调函数
start_property_service();
...省略...
if (bootscript.empty()) {
//解析init.rc文件
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
...省略...
//不要在充电器模式下挂载文件系统或启动核心系统服务
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
//基于属性的当前状态运行所有属性触发器
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
while (true) {
// By default, sleep until something happens.
int epoll_timeout_ms = -1;
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//重启需要重启的服务
restart_processes();
// If there's a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) {
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout_ms = 0;
}
epoll_event ev;
//循环等待事件发生
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
init进程执行完成后,就进入循环等待epoll_wait的状态。init的main函数中做了大量初始化工,比较复杂。我主要学习了如下几个关键点:
-
创建和挂载启动的文件
-
属性服务
-
进程信号处理
-
解析init.rc文件
-
init启动Zygote
3. 创建和挂载启动的文件
在最开始的时候就创建和挂载启动所需要的文件目录,其中挂载了:
-
tempfs
-
devpts
-
proc
-
sysfs
-
selinuxfs
共挂载了这五个系统,这些都是系统运行时的目录,只有当系统运行时才会存在,当系统停止时,这些目录就不会存在。
4. 属性服务
在Windows上有一个注册表管理器,主要采用的是键值对的形式来记录用户、软件的一些信息。即使系统或软件重启,还是能根据之前注册表中的记录,进行初始化工作。在Android系统中,也有一个类似的机制,叫作属性服务。
当某个进程通过property_set()方法修改属性后,init进程会先检查权限,当权限验证通过后,就会去修改相应的属性值,而属性值一旦改变,就会触发相应的触发器(即rc文件中的on开头的语句),在Android Shared Memmo