一 概述
init 进程是 Linux 系统中用户空间的第一个进程,进程号为1.当板子上电,bootloader 启动后,加载并启动 kernel,kernel 启动完后,在用户空间启动 init 进程,然后再通过 init 进程,来解析并读取 init.rc 中的相关配置,从而来启动其他相关进程以及其它操作。
init 进程被赋予了很多重要工作,它的启动主要分为两个阶段:
第一阶段
- ueventd/watchdogd跳转及环境变量设置
- 创建并挂载文件系统
- 初始化日志输出、挂载分区设备
- 启用SELinux安全策略
- 开始第二阶段前的准备
第二阶段
- 初始化属性系统
- 执行SELinux第二阶段并恢复一些文件安全上下文
- 新建epoll并初始化子进程终止信号处理函数
- 设置其他系统属性并开启属性服务
init 进程主要作用总结如下:
- 挂载文件系统并生成相应的设备驱动节点
- 初始化环境变量,日志输出并启用SELinux安全策略
- 处理子进程的终止(signal方式)
- 提供属性服务 解析rc文件启动相关进程和服务
二 kernel 如何启动 init 进程
在 kernel 进入 c 语言阶段后,会开始执行 start_kernel 函数,它负责进行 kernel 正式运行前各个功能的初始化:打印了一些信息、内核工作需要的模块的初始化被依次调用(譬如内存管理、调度系统、异常处理···),最后末尾调用了一个 rest_init 函数启动了三个进程(idle、kernel_init、kthreadd),来开启操作系统的正式运行。如下图所示:
Linux 下有3个特殊的进程,idle(swapper)进程(PID = 0)、init 进程(PID = 1)和 kthreadd(PID = 2),这三个进程是 Linux 内核的基础,后面的所有进线程都是基于这三个进程创建的.
- idle(swapper)进程也是系统的第一个进程,运行在内核态
idle 进程其 pid=0,它是系统创建的第一个进程,也是唯一一个没有通过 fork 产生的进程.完成加载系统后,演变为进程调度、交换,常常被称为交换进程。它是 init 进程,kthreadd 进程的父进程.可以这样总结:原始进程 (pid=0) 创建 init 进程 (pid=1),然后演化成 idle 进程 (pid=0) - init 进程是用户空间的第一个进程,也是用户空间所有进程的父进程
init 进程由 idle 通过 kernel_thread 创建,在内核空间完成初始化后,加载 init 程序,并最终转变为用户空间的 init 进程,完成系统的初始化,是系统中所有其它用户空间进程的祖先进程。在系统启动完成后,init 将演变为守护进程监视系统的其它进程。 - kthreadd 进程是内核一个守护进程,也是内核所有线程的父进程
kthreadd 进程由 idle 通过 kernel_thread 创建,并始终运行在内核空间,负责所有内核线程的调度和管理.它的任务就是管理和调度其他内核线程 kernel_thread,它会循环执行一个 kthreadd 的函数,该函数的作用就是运行 kthread_create_list 全局链表中维护的 kthread,我们调用 kernel_thread 创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接地以 kthreadd 为父进程。
2.1 start_kernel
kernel/msm-4.19/init/main.c
asmlinkage __visible void __init start_kernel(void)
{
....
rest_init();
}
start_kernel 做了许多初始化,最后跳转到 rest_init,在 rest_init 中会创建 init 和 kthreadd 进程。
rest_init 方法也是定义在 kernel/msm-4.19/init/main.c 中,如下:
2.2 rest_init
/* inline修饰的函数类型参数会被内联优化,
noinline修饰的函数类型参数不会被内联优化.*/
static noinline void __ref rest_init(void)
{
........
/* kernel-thread 创建init进程,pid=1,
CLONE_FS 表示子进程和父进程共享相同的文件系统,包括root,当前目录,umask,CLONE_SIGHAND,
子进程与父进程共享相同的信号处理(signal handler)表*/
kernel_thread(kernel_init, NULL, CLONE_FS);//创建init进程
........
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd进程
........
cpu_startup_entry(CPUHP_ONLINE); //调用cpu_idle_loop使的idle进程进入自己的事件循环
}
kernel_thread 会调用 do_fork 函数用于创建进程,进程创建成功后会通过函数指针回调执行 kernel_init 函数;进程创建过程在此就不做分析,我们来看 kernel_init 函数,依然定义在 kernel/msm-4.19/init/main.c 中,代码如下:
2.3 kernel_init
static int __ref kernel_init(void *unused)
{
kernel_init_freeable(); //进行init进程的一些初始化操作
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();// 等待所有异步调用执行完成,,在释放内存前,必须完成所有的异步 __init 代码
free_initmem();// 释放所有init.* 段中的内存
mark_rodata_ro(); //arm64空实现
system_state = SYSTEM_RUNNING;// 设置系统状态为运行状态
numa_default_policy(); // 设定NUMA系统的默认内存访问策略
flush_delayed_fput(); // 释放所有延时的struct file结构体
if (ramdisk_execute_command) {
//ramdisk_execute_command的值为"/init"
if (!run_init_process(ramdisk_execute_command)) //运行根目录下的init程序
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
if (execute_command) {
//execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动
if (!run_init_process(execute_command))
return 0;
pr_err("Failed to execute %s. Attempting defaults...\n",
execute_command);
}
if (!run_init_process("/sbin/init") || //如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,
//就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process("/bin/sh"))
return 0;
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(getname_kernel(init_filename), //do_execve就是执行一个可执行文件
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
kernel_init 主要工作是完成一些 init 的初始化操作,然后去系统根目录下依次找 ramdisk_execute_command 和 execute_command 设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了.Android 系统一般会在根目录下放一个 init 的可执行文件,也就是说 Linux 系统的 init 进程在内核初始化完成后,就直接通过 run_init_process 函数执行 init 这个文件,该可执行文件的源代码在 system/core/init/main.cpp 中。
三 init 进程的启动
前面已经通过 kernel_init,启动了 init 进程,init 进程属于一个守护进程,准确的说,它是 Linux 系统中用户空间的第一个进程,它的进程号为1。它的生命周期贯穿整个 Linux 内核运行的始终。可以通过"adb shell ps |grep init" 的命令来查看 init 的进程号。Android Q(10.0) 的 init 进程入口函数由原先的 init.cpp 调整到了 main.cpp,把各个阶段的操作分离开来,使代码更加简洁明了,接下来我们就从 main 函数开始分析。
3.1 init 进程入口 main 函数
platform/system/core/init/main.cpp
/*
* 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
* 2.main函数有四个参数入口,
*一是参数中有ueventd,进入ueventd_main
*二是参数中有subcontext,进入InitLogging 和SubcontextMain
*三是参数中有selinux_setup,进入SetupSelinux
*四是参数中有second_stage,进入SecondStageMain
*3.main的执行顺序如下:
* (1)ueventd_main init进程创建子进程ueventd,
* 并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件
* (2)FirstStageMain 启动第一阶段
* (3)SetupSelinux 加载selinux规则,并设置selinux日志,完成SELinux相关工作
* (4)SecondStageMain 启动第二阶段
*/
int main(int argc, char** argv) {
//当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1
//1表示true,也就执行ueventd_main,ueventd主要是负责设备节点的创建、权限设定等一些列工作
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//当传入的参数个数大于1时,执行下面的几个操作
if (argc > 1) {
//参数为subcontext,初始化日志系统,
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
//参数为“selinux_setup”,启动Selinux安全策略
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
//参数为“second_stage”,启动init进程第二阶段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 默认启动init进程第一阶段
return FirstStageMain(argc, argv);
}
通过对 system/core/init/README.md 的阅读可以知道 main 函数会执行多次,启动顺序是这样的 FirstStageMain -> SetupSelinux -> SecondStageMain
3.2 ueventd_main
platform/system/core/init/ueventd.cpp
int ueventd_main(int argc, char** argv) {
//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
umask(000);
//初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程还没有起来,
//采用kernel的log系统,打开的设备节点/dev/kmsg, 那么可通过cat /dev/kmsg来获取内核log。
android::base::InitLogging(argv, &android::base::KernelLogger);
//注册selinux相关的用于打印log的回调函数
SelinuxSetupKernelLogging();
SelabelInitialize();
//解析xml,根据不同SOC厂商获取不同的hardware rc文件
auto ueventd_configuration = ParseConfig({
"/ueventd.rc", "/vendor/ueventd.rc",
"/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
//冷启动
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, uevent_handlers);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->ColdbootDone();
}
//忽略子进程终止信号
signal(SIGCHLD, SIG_IGN);
// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
// for SIGCHLD above.
//在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
//监听来自驱动的uevent,进行“热插拔”处理
uevent_listener.