Android系统启动流程--init进程的启动流程
这可能是个系列文章,用来总结和梳理Android系统的启动过程,以加深对Android系统相对全面的感知和理解(基于Android11)。
1.启动电源,设备上电
引导芯片代码从预定义的地方(固化在ROM,全称Read Only Memory,是一种只能读出事先所存的数据的固态半导体存储器)开始执行,加载引导程序BootLoader到RAM(RAM 是随机存取存储器,它的特点是易挥发性,即掉电失忆)并执行该程序。
2.引导程序BootLoader
BootLoader是用来引导Android操作系统启动的程序,相当于windows中的bose程序,它创建了Android系统中第一个进程idle(pid=0)。
3.Linux内核启动
idle进程创建后中做了初始化进程管理、内存管理、加载Binder Driver Display、Camera Driver等相关工作,并创建了以下两个进程(线程),干完这些工作之后idle进程就空闲下来了。
①创建kthread(pid=2)线程,在其中创建kworker线程,软中断线程ksoftirqd,thermal守护线程(kernel中没有进程线程之分),内核线程鼻祖。
②init(pid=1)进程,它是用户空间的第一个进程,由此开始后面创建的进程都是由init或其子进程fork而来。
以上3步简单了解即可,重点从启动init进程开始。
4. init进程启动
当kernel启动后会开始启动init进程:
//文件路径: kernel/common/init/main.cstatic int __ref kernel_init(void *unused)
{int ret;//执行/bin/init程序if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_process("/etc/init") ||!try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh"))return 0;
}
"/bin/init"是在Android系统源码编译时编译出的一个可执行程序,路径为Android设备上的"system/bin/init",而这个init程序则是由system/core/init/main.cpp文件编译生成的(可查看同目录下的Android.bp,其中描述了编译规则),所以执行这个init程序其实就是运行到了main.cpp的main()方法。
//文件路径 system/core/init/main.cpp//此方法会执行多次
int main(int argc, char argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#endifif (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}if (argc > 1) {if (!strcmp(argv[1], "subcontext")) {android::base::InitLogging(argv, &android::base::KernelLogger);const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();return SubcontextMain(argc, argv, &function_map);}//SELinux 是由美国NSA(国安局)和 SCC 开发的 Linux的一个扩张强制访问控制安全模块if (!strcmp(argv[1], "selinux_setup")) { //(是从FirstStageMain()中调用到这里)//step2:启动selinuxreturn SetupSelinux(argv);}//step3:if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}//step1:第一次进入argv为空return FirstStageMain(argc, argv);
}
我们先看第一步会做哪些操作:
//文件路径:system/core/init/first_stage_init.cppint FirstStageMain(int argc, char argv) {//安全处理,如果init进程挂掉,会重启引导加载程序if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}//创建和挂载启动所需的目录文件CHECKCALL(clearenv());CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));// 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.CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));CHECKCALL(mkdir("/dev/pts", 0755));CHECKCALL(mkdir("/dev/socket", 0755));CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));...//把标准输入输出重定向到dev/null文件SetStdioToDevNull(argv);// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually// talk to the outside world...// 初始化日志InitKernelLogging(argv);...//启动selinuxconst char* path = "/system/bin/init";const char* args[] = {path, "selinux_setup", nullptr};auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);close(fd);execv(path, const_cast<char>(args)); //这里执行就会运行到上面提到的main()中selinux_setup分支...return 1
}
总结:FirstStageMain()中主要做了以下几件事
- 挂载创建系统所需的文件和目录
- 重定向输入输出
- 初始化内核日志打印
接下来是第二步:
//文件路径: system/core/init/selinux.cppint SetupSelinux(char argv) {//重定向(初始化)输入输出SetStdioToDevNull(argv);//初始化kernel日志打印InitKernelLogging(argv);if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();MountMissingSystemPartitions();// Set up SELinux, loading the SELinux policy.// 启动selinux安全策略SelinuxSetupKernelLogging();SelinuxInitialize();// We're in the kernel domain and want to transition to the init domain. File systems that// store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,// but other file systems do. In particular, this is needed for ramdisks such as the// recovery image for A/B devices.if (selinux_android_restorecon("/system/bin/init", 0) == -1) {PLOG(FATAL) << "restorecon failed of /system/bin/init failed";}setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);//再次执行init程序,这次走到second_stage分支const char* path = "/system/bin/init";const char* args[] = {path, "second_stage", nullptr};execv(path, const_cast<char>(args));// execv() only returns if an error happened, in which case we// panic and never return from this function.PLOG(FATAL) << "execv(\\"" << path << "\\") failed";return 1;
}
总结:SetupSelinux()中主要做的几件事:
- 重定向输入输出
- 初始化内核日志打印
- 初始化selinux相关内容
接下来是第三步:
//文件路径:system/core/init/init.cppint SecondStageMain(int argc, char argv) {//重启判断,是否重新启动引导程序if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}...//重定向(初始化)输入输出SetStdioToDevNull(argv);//初始化kernel日志打印InitKernelLogging(argv);//初始化属性服务,property_service.cppPropertyInit();// 再次初始化selinuxSelinuxSetupKernelLogging();SelabelInitialize();SelinuxRestoreContext();//处理子进程的终止信号,及时清除挂掉的进程,防止出现僵尸进程InstallSignalFdHandler(&epoll);InstallInitNotifier(&epoll);StartPropertyService(&property_fd);//建立linux命令与实现其功能的函数间的关系const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();Action::set_function_map(&function_map);//解析init.rc文件,将解析出的内容存储到下面两个对象中ActionManager& am = ActionManager::GetInstance();ServiceList& sm = ServiceList::GetInstance();LoadBootScripts(am, sm);//循环处理init.rc脚本中的command命令,处理完就进入等待while (true) {if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {//内部遍历执行每个action中携带的command对应的执行函数am.ExecuteOneCommand();}}}
总结:SecondStageMain()中主要做的几件事:
- 重定向输入输出
- 初始化内核日志打印
- 初始化属性服务
- 监听并处理挂掉的子进程
- 建立linux命令与实现方法的联系
- 初始化selinux相关内容
- 解析init.rc文件
- 执行init.rc中的action
init进程总结:
init进程会走main.cpp,然后分阶段去执行main()函数,这个调用是循环调用的方式,最后一个阶段是SecondStageMain(),里面会执行一个非常重要的方法LoadBootScripts(am,sm),这个方法解析了一个init.rc文件,并将这些命令写到了am与sm中,在while循环里通过ExecuteOneCommand去执行这些命令。其中就包含了启动zygote进程的命令。当然这个while是个死循环以保证init进程的存活,那要是没事做怎么办?那就睡眠等待,用epoll.wait.
init做的事:
- 挂载文件
- 设置selinux
- 启动属性服务
- 解析init.rc,执行脚本中一行一行的Linux命令来启动脚本
- 循环处理脚本--包括启动zygote、serviceManager进程
- 守护系统关键进程:如蓝牙、铃声、接打电话、应用安装等进程名结尾带d的系统进程。
到这里Android系统init进程的启动就完成了,本篇的最后提到了解析init.rc文件,从这里会引出后续两个比较重要的进程的启动流程:
1.ServiceManager进程的启动流程
2.zygote进程的启动流程