Android 13 init进程(一)

简介: 学习笔记

当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。

init进程启动主要分为两个阶段:

第一个阶段负责:

  • 创建文件系统目录并挂载相关的文件系统
  • 初始化日志输出
  • 启用SELinux安全策略
  • 为第二阶段做准备

第二阶段负责:

  • 创建进程会话密钥、并初始化属性系统
  • 执行SELinux第二阶段、并恢复一些文件安全上下文
  • 新建epoll、并初始化子进程终止信号处理函数
  • 设置其他系统属性、并开启属性服务
  • 解析init.rc等文件,建立rc文件的action、service,启动其他进程

init进程如何被启动?

init进程是在Kernel启动后,启动的第一个用户空间进程,PID为1


/android/kernel-4.19/init/main.c

staticint__ref kernel_init(void*unused)

{

    intret;

 

    kernel_init_freeable();//进行init进程的一些初始化操作

    /* need to finish all async __init code before freeing the memory */

    async_synchronize_full();//等待所有异步调用执行完成,在释放内存前,必须完成所有的异步 __init 代码

    ftrace_free_init_mem();

    jump_label_invalidate_initmem();

    free_initmem();//释放所有init.*中的内存

    mark_readonly();

 

    /*

     * Kernel mappings are now finalized - update the userspace page-table

     * to finalize PTI.

     */

    pti_finalize();

 

    system_state = SYSTEM_RUNNING;//设置系统状态为运行状态

    numa_default_policy();//设定NUMA系统的默认内存访问策略

 

    rcu_end_inkernel_boot();

 

    bootprof_log_boot("Kernel_init_done");

 

    if(ramdisk_execute_command) {//ramdisk_execute_command的值为“/init”

        ret = run_init_process(ramdisk_execute_command);//运行根目录下的init进程 *****

        if(!ret)

            return0;

        pr_err("Failed to execute %s (error %d)\n",

               ramdisk_execute_command, ret);

    }

 

    /*

     * We try each of these until one succeeds.

     *

     * The Bourne shell can be used instead of init if we are

     * trying to recover a really broken machine.

     */

    if(execute_command) {//execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动

        ret = run_init_process(execute_command);

        if(!ret)

            return0;

        panic("Requested init %s failed (error %d).",

              execute_command, ret);

    }

    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"))//如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,

    //就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动

 

        return0;

 

    panic("No working init found.  Try passing init= option to kernel. "

          "See Linux Documentation/admin-guide/init.rst for guidance.");

}

在/kernel/init/mian.c#kernel_init()方法调用了run_init_process()进行启动init进程

init进程入口

在Android Q(10.0)之前的init入口函数是init.cpp,从Android Q(10.0)开始init的入口函数是main.cpp,把各个阶段的操作分离开来,是代码更加简洁。

进入到main.cpp#main()

/android/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  启动第二阶段

 */

 

intmain(intargc, char** argv) {

#if __has_feature(address_sanitizer)

    __asan_set_error_report_callback(AsanReportCallback);

#endif

    // Boost prio which will be restored later

    setpriority(PRIO_PROCESS, 0, -20);

    

    //当argv[0]的内容为ueventd时,strcmp的值为0,ueventd主要是负责设备节点的创建、权限设定等一些列工作

    if(!strcmp(basename(argv[0]), "ueventd")) {

        returnueventd_main(argc, argv);

    }

 

    //当传入的参数个数大于1时

    if(argc > 1) {

        //参数为subcontext,初始化日志系统

        if(!strcmp(argv[1], "subcontext")) {

            android::base::InitLogging(argv, &android::base::KernelLogger);

            constBuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

 

            returnSubcontextMain(argc, argv, &function_map);

        }

        //参数为selinux_setup,启动Selinux安全策略

        if(!strcmp(argv[1], "selinux_setup")) {

            returnSetupSelinux(argv);

        }

 

        //参数为“sencond_stage”,启动init进程第二阶段

        if(!strcmp(argv[1], "second_stage")) {

            returnSecondStageMain(argc, argv);

        }

    }

    //默认启动init进程第一阶段

    returnFirstStageMain(argc, argv);

}

ueventd_main()

Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。所以,建立Android中设备节点文件需要init进程完成,为此init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。

ueventd通过两种方式创建设备节点文件:

第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,同一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。

第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。

进入ueventd.cpp#ueventd_main()

/android/system/core/init/ueventd.cpp

intueventd_main(intargc, char** argv) {

    /*

     * init sets the umask to 077 for forked processes. We need to

     * create files with exact permissions, without modification by

     * the umask.

     */

    //设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666

    umask(000);

 

    //初始化内核日志,位于节点/dev/kmsg,此时logd、logcat进程还没有起来

    //采用kernel的log系统,打开的设备节点/dev/kmsg,那么可通过cat /dev/kmsg来获取内核log

    android::base::InitLogging(argv, &android::base::KernelLogger);

 

    LOG(INFO) << "ueventd started!";

 

    //注册selinux相关的用于打印log的回调函数

    SelinuxSetupKernelLogging();

    SelabelInitialize();

 

    std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;

    //解析xml,根据不同SOC厂商获取不同的hardware rc文件

    auto ueventd_configuration = GetConfiguration();

 

    uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(

            std::move(ueventd_configuration.dev_permissions),

            std::move(ueventd_configuration.sysfs_permissions),

            std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));

    uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(

            std::move(ueventd_configuration.firmware_directories),

            std::move(ueventd_configuration.external_firmware_handlers)));

 

    //冷启动

    if(ueventd_configuration.enable_modalias_handling) {

        std::vector<std::string> base_paths = {"/odm/lib/modules""/vendor/lib/modules"};

        uevent_handlers.emplace_back(std::make_unique<ModaliasHandler>(base_paths));

    }

    UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);

 

    if(!android::base::GetBoolProperty(kColdBootDoneProp, false)) {

        ColdBoot cold_boot(uevent_listener, uevent_handlers,

                           ueventd_configuration.enable_parallel_restorecon);

        cold_boot.Run();

    }

 

    for(auto& uevent_handler : uevent_handlers) {

        uevent_handler->ColdbootDone();

    }

 

    //忽略子进程终止信号

    signal(SIGCHLD, SIG_IGN);

 

    //在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级

    while(waitpid(-1, nullptr, WNOHANG) > 0) {

    }

 

    // Restore prio before main loop

    setpriority(PRIO_PROCESS, 0, 0);

    //监听来自驱动的uevent,进行“热插拔”处理

    uevent_listener.Poll([&uevent_handlers](constUevent& uevent) {

        for(auto& uevent_handler : uevent_handlers) {

            uevent_handler->HandleUevent(uevent);

        }

        returnListenerAction::kContinue;

    });

 

    return0;

}

init进程启动第一阶段first_stage_init.cpp

主要负责:

  • 创建文件系统目录并挂载相关的文件系统
  • 初始化日志输出
  • 启用SELinux安全策略
  • 为第二阶段做准备

/android/system/core/init/first_stage_init.cpp

intFirstStageMain(intargc, char** argv) {

    //init crash时重启引导加载程序

 

    //这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统

    if(REBOOT_BOOTLOADER_ON_PANIC) {

        InstallRebootSignalHandlers();

    }

 

    boot_clock::time_point start_time = boot_clock::now();

 

    std::vector<std::pair<std::string, int>> errors;

    #define CHECKCALL(x) \

    if((x) != 0) errors.emplace_back(#x " failed"errno);

 

    // Clear the umask.

    //清空文件权限

    umask(0);

 

    CHECKCALL(clearenv());

    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));

 

    //在RAM内存上获取基本的文件系统,剩余的被rc文件所用

    CHECKCALL(mount("tmpfs""/dev""tmpfs", MS_NOSUID, "mode=0755"));

    CHECKCALL(mkdir("/dev/pts", 0755));

    CHECKCALL(mkdir("/dev/socket", 0755));

    CHECKCALL(mkdir("/dev/dm-user", 0755));

    CHECKCALL(mount("devpts""/dev/pts""devpts", 0, NULL));

    #define MAKE_STR(x) __STRING(x)

    CHECKCALL(mount("proc""/proc""proc", 0, "hidepid=2,gid="MAKE_STR(AID_READPROC)));

    #undef MAKE_STR

    //非特权应用不能使用Android cmdline

    CHECKCALL(chmod("/proc/cmdline", 0440));

    std::string cmdline;

    android::base::ReadFileToString("/proc/cmdline", &cmdline);

    // Don't expose the raw bootconfig to unprivileged processes.

    chmod("/proc/bootconfig", 0440);

    std::string bootconfig;

    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);

    gid_t groups[] = {AID_READPROC};

    CHECKCALL(setgroups(arraysize(groups), groups));

    CHECKCALL(mount("sysfs""/sys""sysfs", 0, NULL));

    CHECKCALL(mount("selinuxfs""/sys/fs/selinux""selinuxfs", 0, NULL));

 

    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));

 

    ifconstexpr (WORLD_WRITABLE_KMSG) {

        CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));

    }

 

    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));

    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));

 

    //这对于日志包装器是必需的,它在ueventd运行之前被调用

    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));

    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

 

    //在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,

    //只需要在第二阶段通过rc文件解析来加载

    CHECKCALL(mount("tmpfs""/mnt""tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,

        "mode=0755,uid=0,gid=1000"));

    //创建可供读写的vendor目录

    CHECKCALL(mkdir("/mnt/vendor", 0755));

 

    CHECKCALL(mkdir("/mnt/product", 0755));

 

    // 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,

    // 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级

    CHECKCALL(mount("tmpfs""/debug_ramdisk""tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,

        "mode=0755,uid=0,gid=0"));

 

    // /second_stage_resources is used to preserve files from first to second

    // stage init

    CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,

    "mode=0755,uid=0,gid=0"))

    #undef CHECKCALL

 

    //把标准输入、标准输出和标准错误重定向到空设备文件“/dev/null”

    SetStdioToDevNull(argv);

    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually

// talk to the outside world...

#ifdef MTK_LOG

#ifndef MTK_LOG_DISABLERATELIMIT

if(cmdline.find("init.mtklogdrl=1") != std::string::npos)

SetMTKLOGDISABLERATELIMIT();

#else

SetMTKLOGDISABLERATELIMIT();

#endif // MTK_LOG_DISABLERATELIMIT

 

if(GetMTKLOGDISABLERATELIMIT())

InitKernelLogging_split(argv);

else

InitKernelLogging(argv);

#else

//在/dev目录下挂载好tmpfs以及kmsg

//这样就可以初始化/kernel Log系统,供用户打印log

InitKernelLogging(argv);

#endif

 

......

 

/*

初始化一些必须的分区

主要作用是去解析/proc/device-tree/firmware/android/fstab

然后得到“/system”,“/vendor”,“/odm”三个目录的挂载信息

*/

if(!DoFirstStageMount(!created_devices)) {

LOG(FATAL) << "Failed to mount required partitions early ...";

}

 

structstat new_root_info;

if(stat("/", &new_root_info) != 0) {

PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";

old_root_dir.reset();

}

 

if(old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {

FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);

}

 

SetInitAvbVersionInRecovery();

 

setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),

1);

 

//启动init进程,传入参数selinux_steup

//执行命令:/system/bin/init selinux_setup

constchar* path = "/system/bin/init";

constchar* 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));

 

// execv() only returns if an error happened, in which case we

// panic and never fall through this conditional.

PLOG(FATAL) << "execv(\""<< path << "\") failed";

 

return1;

}

加载SELinux规则

SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」

和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。

SElinux有两种工作模式:

  1. permissive,所有的操作都被允许(即没有MAC),但是如果违法权限的话,会记录日志,一般eng模式用
  2. enforcing,所有操作都会进行权限检查,一般user和user-debug模式用

不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce文件,0表示permissive 1表示enforcing

SetupSelinux:初始化selinux,加载SElinux规则,配置SWLinux相关log输出,并启动第二阶段

/android/system/core/init/selinux.cpp

/*此函数初始化selinux,然后执行init以在init selinux中运行*/

intSetupSelinux(char** argv) {

#ifdef JOURNEY_FEATURE_ROOT_MODE

    initJourneyRootMode();

#endif

    SetStdioToDevNull(argv);

#ifdef MTK_LOG

#ifndef MTK_LOG_DISABLERATELIMIT

    {

        std::string cmdline;

        android::base::ReadFileToString("/proc/cmdline", &cmdline);

 

        if(cmdline.find("init.mtklogdebuggable=1") != std::string::npos)

            SetMTKLOGDISABLERATELIMIT();

    }

#else

    SetMTKLOGDISABLERATELIMIT();

#endif // MTK_LOG_DISABLERATELIMIT

    if(GetMTKLOGDISABLERATELIMIT())

        InitKernelLogging_split(argv);

    else

        InitKernelLogging(argv);

#else

    //初始化Kernel日志

    InitKernelLogging(argv);

#endif

 

    //Debug版本init crash时重启引导加载程序

    if(REBOOT_BOOTLOADER_ON_PANIC) {

        InstallRebootSignalHandlers();

    }

 

    boot_clock::time_point start_time = boot_clock::now();

 

    MountMissingSystemPartitions();

 

#ifdef MTK_LOG

    if(GetMTKLOGDISABLERATELIMIT())

        SelinuxSetupKernelLogging_split();

    else

        SelinuxSetupKernelLogging();

#else

    //注册回调,用来设置需要写入kmsg的selinux日志

    SelinuxSetupKernelLogging();

#endif

 

    LOG(INFO) << "Opening SELinux policy";

 

    // Read the policy before potentially killing snapuserd.

    std::string policy;

    ReadPolicy(&policy);

 

    auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();

    if(snapuserd_helper) {

        // Kill the old snapused to avoid audit messages. After this we cannot

        // read from /system (or other dynamic partitions) until we call

        // FinishTransition().

        snapuserd_helper->StartTransition();

    }

 

    LoadSelinuxPolicy(policy);

 

    if(snapuserd_helper) {

        // Before enforcing, finish the pending snapuserd transition.

        snapuserd_helper->FinishTransition();

        snapuserd_helper = nullptr;

    }

 

    //加载SElinux规则

    SelinuxSetEnforcement();

 

    // 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,进入到第二阶段

    constchar* path = "/system/bin/init";

    constchar* 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";

 

    return1;

}

SelinuxSetEnforcement():加载SeLinux规则

/android/system/core/init/selinux.cpp

voidSelinuxSetEnforcement() {

    //获取当前Kernel的工作模式

    boolkernel_enforcing = (security_getenforce() == 1);

    //获取工作模式的配置

    boolis_enforcing = IsEnforcing();

    //如果当前的工作模式与配置的不同,就将当前的工作模式改掉

    if(kernel_enforcing != is_enforcing) {

        if(security_setenforce(is_enforcing)) {

            PLOG(FATAL) << "security_setenforce("<< (is_enforcing ? "true""false")

                << ") failed";

        }

    }

 

    if(auto result = WriteFile("/sys/fs/selinux/checkreqprot""0"); !result.ok()) {

        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: "<< result.error();

    }

}

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
24天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
7月前
|
Java Linux Android开发
理解Android进程创建流程
理解Android进程创建流程
53 0
|
7月前
|
Shell Android开发
Android init language与init.rc初始化脚本
Android init language与init.rc初始化脚本
53 0
|
12月前
|
Unix Linux Android开发
Android C++系列:Linux进程间通信(二)
mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。
73 0
|
12月前
|
Linux Android开发 C++
Android C++系列:Linux进程间通信(一)
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不 到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用 户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程 间通信(IPC,InterProcess Communication)。
58 0
|
12月前
|
Shell Linux Android开发
Android C++系列:Linux进程(三)
如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时 的进程状态称为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程,正常情况下,僵 尸进程都立刻被父进程清理了,为了观察到僵尸进程
95 0
|
12月前
|
Linux Android开发 C++
Android C++系列:Linux进程(二)
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支), 子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的 用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建 新进程,所以调用exec前后该进程的id并未改变。
139 0
|
12月前
|
Shell Linux C语言
Android C++系列:Linux进程(一)
我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信 息,Linux内核的进程控制块是task_struct结构体。现在我们全面了解一下其中都有哪 些信息。
97 0
|
Java Android开发
|
3天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式