Android的Init进程分析

234 阅读11分钟

在android系统的启动过程中,当bootloader启动后启动kernel,kernel启动完成后在用户空间启动init进程。
本文以Android 13代码为例。
1,相关概念
1)UBoot:U-Boot是一种用于嵌入式系统中的Bootloader。Bootloader是在操作系统运行之前执行的独立于内核的程序,BootLoader完成由硬件启动到操作系统启动的过渡,从而为操作系统提供基本的运行环境。通过它,可以初始化硬件设备、建立内存空间的映射表,从而建立适当的软硬件环境,为最终调用操作系统内核做好准备。U-Boot的主要运行任务包括将内核映像从硬盘上读到RAM中,并跳转到内核的入口点去运行,即开始启动操作系统。
2)boot.img:boot.img是Android系统启动所必须加载的文件。它包含了Android系统启动所需的内核(kernel)和ramdisk(根文件系统的一部分)。boot.img存放的就是Linux内核和一个根文件系统。然后Linux内核会执行整个系统的初始化,然后装载根文件系统。当Android设备启动时,boot.img会被加载到内存中,并由引导程序执行。它首先会解压ramdisk到内存中,然后启动内核,最终加载Android系统的其他部分。
2,kernel的启动概述
在kernel层,启动swapper进程(pid=0),该进程又称为idle进程,linux系统中,当cpu上没有要执行的任务时,通常要运行swapper进程,即处于idle状态。 swapper进程是系统初始化过程Kernel由无到有开创的第一个进程,用于初始化进程管理、内存管理,加载Display、Camera Driver、Binder Driver等相关工作。
内核初始化阶段结束时,通过调用start_kernel函数进入内核启动阶段,start_kernel函数是Linux内核通用的启动函数,也是汇编代码执行完毕后的第一个C语言函数,它的实现代码位于init/main.c中。
在kernel层,通过kernel_thread函数首先创建了init进程以使其成为1号进程。0号swapper进程通过调用kernel_thread(kernel_init, NULL, CLONE_FS)执行定义在kernel/common/init/main.c中的kernel_init函数,在kernel_init函数中,加载init,进入用户空间。在/kernel/init/mian.c#kernel_init()方法调用了run_init_process()进行启动init进程。
内核启动后在kernel层会启动一个pid=2的kthread进程。在kernel层启动的kthread进程(pid=2)是Linux系统的内核进程,会创建内核工作线程kworkder,软中断线程ksoftirqd,thermal等一系列内核守护进程。kthreadd进程是所有内核进程的父进程,kthread进程是linux内核管理者,主要负责内核线程的调度和管理,由swapper进程通过kernel_thread创建。
3,kernel启动分析
内核空间:start_kernel -> rest_init -> kernel_thread:kernel_init -> init_post -> run_init_process
用户空间:do_execve -> linuxrc ->启动其它用户进程
内核启动:start_kernel -> rest_init -> kernel_thread:kernel_init
启动init进程:init_post -> run_init_process -> do_execve -> linuxrc ->启动其它用户进程
在rest_init函数中通过kernel_thread函数首先创建了init进程以使其成为1号进程。在rest_init函数中通过kernel_thread启动了kernel_init和kthreadd这两个内核线程。 kernel启动完成后,在用户空间启动init进程,再通过init进程来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。
init进程一开始运行于内核态,属于一个内核线程,后来init进程挂载根文件系统,并运行应用程序init程序后,init进程才从内核态转变为用户态。init进程执行init程序完成内核态到用户态的转变。init进程的入口函数是system/core/init/main.cpp 的 main 函数。1号kernel_init进程完成linux的各项配置后,就会在/sbin,/etc,/bin寻找init程序来运行,该init程序会替换kernel_init进程,此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。
一个init进程先后有两种状态,init进程刚开始运行的时候是内核态,它属于一个内核线程,然后运行一个用户态下面的程序后,把自己强行转成用户态,init进程完成了从内核态到用户态的过渡,因此后续的其他进程都可以工作在用户态。init进程在内核态下的工作内容:主要是挂载根文件系统,并试图找到用户态下的那个init程序(init进程是早于init程序运行的)。 init进程在用户态下的工作内容:init进程大部分有意义的工作都是在用户态下进行的。其他所有的用户进程都直接或者间接派生自init进程。
init进程如何从内核态跳跃到用户态:init进程处于内核态时,通过函数kernel_execve来执行一个用户空间编译链接的应用程序就跳跃到用户态了。跳跃过程中进程号没有改变,一直是进程1。跳跃过程是单向的,一旦执行init程序转到用户态,整个操作系统就算真正运转起来了,以后只能在用户态下工作,用户态下想要进入内核态只能通过调用API。init进程要把自己转成用户态就必须运行一个用户态的应用程序,要运行这个应用程序就必须得找到这个应用程序,要找到这个应用程序就必须得挂载根文件系统,因为所有的应用程序都在文件系统中。
0号swapper进程通过调用kernel_thread(kernel_init, NULL, CLONE_FS)执行定义在kernel/common/init/main.c中的kernel_init函数,在kernel_init函数中,加载init,进入用户空间,init进程执行init程序完成内核态到用户态的转变。

static int __ref kernel_init(void *unused) {  
//ramdisk_execute_command的值为"/init",如果ramdisk_execute_command不为0,就执行该命令成为init User Process.   
	if (ramdisk_execute_command) {  
            ret = run_init_process(ramdisk_execute_command);   
            if (!ret) return 0;  
            pr_err("Failed to execute %s (error %d)\n",
                   ramdisk_execute_command, ret);
    }   
//execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动,如果execute_command不为0,就执行该命令成为init User Process.  
	if (execute_command) {  
            ret = run_init_process(execute_command);  
            if (!ret) return 0;   
            panic("Requested init %s failed (error %d).",
                  execute_command, ret);   
    }  
/** 如果上述都不成立,就依序执行如下指令  
run_init_process(“/sbin/init”);
run_init_process(“/etc/init”);
run_init_process(“/bin/init”);
run_init_process(“/bin/sh”);  
也就是说会按照顺序从/sbin/init, /etc/init, /bin/init 与 /bin/sh依序执行第一个 init User Process.  */  
	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;  
	//如果都找不到可以执行的 init Process,就会进入Kernel Panic.  
	panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}
static int run_init_process(const char *init_filename){
    const char *const *p;
    argv_init[0] = init_filename;
    return kernel_execve(init_filename, argv_init, envp_init); //kernel_execve是内核空间调用用户空间的应用程序的函数。
}

内核通过调用kernel_init()函数来启动第一个用户空间进程。这个函数会检查启动参数,找到要启动的第一个进程的名称(在Android中,这通常是/init)。 然后,内核会调用do_execve()函数来执行/init程序。这个函数会加载/init程序的二进制文件到内存,设置必要的寄存器(如程序计数器),并开始执行init程序的入口点(即main函数)。当init进程的main函数(位于system/core/init/main.cpp)开始执行时,它会读取并解析init.rc(以及可能的其他配置文件),这些文件包含了系统服务和进程的启动指令。随着init进程的启动和配置,系统服务(如zygote、system_server等)也会相继启动。
4)system/core/init/main.cpp分析
在int main(int argc, char** argv)函数中,分为3个阶段:挂载相关文件系统FirstStageMain(argc, argv);创建安全增强型Linux(SELinux)SetupSelinux(argv);解析init.rc文件、提供服务、创建epoll与处理子进程的终止等SecondStageMain(argc, argv);
在FirstStageMain阶段,主要是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。

CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); //挂载tmpfs到/dev,tmpfs是一种基于内存的文件系统,它使用虚拟内存来存储文件。在Android系统中,tmpfs用于存储临时文件和设备文件,因为它提供了快速的访问速度。由于tmpfs是内存中的文件系统,当系统重启时,其中的内容会丢失。
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));//挂载devpts到/dev/pts,devpts是一个伪文件系统,用于提供对终端设备的访问。在Unix和类Unix系统中,每个终端会话都需要一个伪终端(pty)对,devpts文件系统就是用来管理这些伪终端设备的。它允许用户空间程序(如终端模拟器)创建和管理伪终端设备。
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));//proc是一个虚拟文件系统,它提供了一个接口来访问内核数据结构。通过/proc目录,用户可以获取系统信息(如进程信息、系统状态等),而不需要直接访问内核内存。这对于系统监控和调试非常有用。
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));//sysfs是一个提供内核设备信息给用户空间的文件系统。它允许用户空间程序查询和修改内核中设备的属性。这对于设备驱动程序的编写和调试非常有帮助。
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));//SELinux是一个为Linux提供访问控制安全策略的安全模块。

CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));//mknod创建/dev/kmsg设备节点

InitKernelLogging(argv);//初始化日志系统

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr}; //准备要执行的程序路径(/system/bin/init)和参数("selinux_setup")。
auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); //打开/dev/kmsg用于写入,然后将标准输出和标准错误重定向到这个文件描述符,最后关闭原始的文件描述符。
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
execv(path, const_cast<char**>(args)); //使用execv函数执行/system/bin/init程序,并传递"selinux_setup"作为参数。注意这里使用了const_cast来移除args数组的const限定符,因为execv需要非const的指针数组。

在SetupSelinux阶段

int SetupSelinux(char** argv) {
    SetStdioToDevNull(argv);//重定向标准输入输出到/dev/null,这通常是为了在启动过程中避免不必要的日志输出到控制台。
    InitKernelLogging(argv);
    ReadPolicy(&policy);//读取SELinux策略 (ReadPolicy(&policy);):从某个源(如文件或预编译的二进制数据)读取SELinux策略。
    LoadSelinuxPolicy(policy);//加载SELinux策略 (LoadSelinuxPolicy(policy);):将读取到的SELinux策略加载到系统中。
    SelinuxSetEnforcement();//设置SELinux为强制模式 (SelinuxSetEnforcement();):在策略加载后,将SELinux设置为强制模式,此时系统将根据策略强制执行访问控制。
    
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args)); //通过execv函数以second_stage参数重新启动init进程。这是Android系统启动过程中的一个关键步骤,标志着系统进入了第二阶段的初始化过程。
   // execv() only returns if an error happened, in which case we panic and never return from this function.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
}

在SecondStageMain阶段,/system/core/init/init.cpp中

int SecondStageMain(int argc, char** argv) {
    Epoll epoll; //初始化epoll实例,用于处理I/O事件
    ActionManager& am = ActionManager::GetInstance(); //获取ActionManager和ServiceList的实例,准备执行系统服务和动作
    ServiceList& sm = ServiceList::GetInstance();
    LoadBootScripts(am, sm);
    
    am.QueueEventTrigger("early-init");//ActionManager这个类中的QueueEventTrigger方法,负责触发init.rc中的Action
    am.QueueEventTrigger("init"); //触发init事件以开始启动过程
    std::string bootmode = GetProperty("ro.bootmode", ""); 
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
    //为了保证init进程活着,有一个while(true)的死循环。epoll.Wait(epoll_timeout)等待
    while (true) { //这是一个无限循环,init进程在这里处理各种事件和任务,如执行命令、处理进程动作、等待epoll事件等
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        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)();
        }

    }//while

}

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list); //创建一个Parser对象,用于解析启动脚本。
    std::string bootscript = GetProperty("ro.boot.init_rc", ""); //尝试从系统属性"ro.boot.init_rc"中获取启动脚本的路径。如果属性不存在或为空,则使用空字符串作为默认值。 
    if (bootscript.empty()) { //如果为空,则按照预定义的路径顺序尝试解析启动脚本。 
        parser.ParseConfig("/system/etc/init/hw/init.rc");
    if (!parser.ParseConfig("/system/etc/init")) { //然后尝试解析/system/etc/init目录,如果解析失败,则将这个路径添加到late_import_paths列表中,以便稍后处理。
        late_import_paths.emplace_back("/system/etc/init");
    }
    // late_import is available only in Q and earlier release. As we don't
    // have system_ext in those versions, skip late_import for system_ext.
    parser.ParseConfig("/system_ext/etc/init");
    if (!parser.ParseConfig("/vendor/etc/init")) {
        late_import_paths.emplace_back("/vendor/etc/init");
    }
    if (!parser.ParseConfig("/odm/etc/init")) {
        late_import_paths.emplace_back("/odm/etc/init");
    }
    if (!parser.ParseConfig("/product/etc/init")) {
        late_import_paths.emplace_back("/product/etc/init");
    }
} else {
    parser.ParseConfig(bootscript); //对于Android Q及更早版本,没有system_ext分区,因此直接解析/system_ext/etc/init。  
}

}