Android系统启动系列2 init进程

一 概述

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.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值