Android 13 init进程(一)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 学习笔记

当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日志并进行多维度分析。
相关文章
|
2月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
326 4
|
3月前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
64 1
|
3月前
|
Android开发
我的Android进阶修炼:安卓启动流程之init(1)
本文深入分析了Android系统中的init进程,包括其源码结构、主要功能以及启动流程的详细注解,旨在帮助读者理解init作为用户空间的1号进程在Android启动过程中的关键作用。
65 1
|
3月前
|
Android开发 开发者 Kotlin
Android 多进程情况下判断应用是否处于前台或者后台
本文介绍在多进程环境下判断Android应用前后台状态的方法。通过`ActivityManager`和服务信息`RunningAppProcessInfo`可有效检测应用状态,优化资源使用。提供Kotlin代码示例,帮助开发者轻松集成。
272 8
|
5月前
|
大数据 Linux Android开发
Android ParcelFileDescriptor实现进程间通信
Android ParcelFileDescriptor实现进程间通信
92 0
|
6月前
|
XML 前端开发 Android开发
Android架构设计——MVC(1),Android多进程从头讲到尾
Android架构设计——MVC(1),Android多进程从头讲到尾
|
Java Linux Android开发
理解Android进程创建流程
理解Android进程创建流程
114 0
|
6月前
|
安全 Linux API
Android进程与线程
Android进程与线程
51 0
|
Shell Android开发
Android init language与init.rc初始化脚本
Android init language与init.rc初始化脚本
88 0
|
Unix Linux Android开发
Android C++系列:Linux进程间通信(二)
mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。
108 0

热门文章

最新文章

相关实验场景

更多
下一篇
无影云桌面