Android 10.0系统启动之init进程-[Android取经之路]

原文链接:https://blog.csdn.net/yiranfeng/article/details/103549394


摘要:init进程是linux系统中用户空间的第一个进程,进程号为1.当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。

 

阅读本文大约需要花费50分钟。

文章的内容主要还是从源码进行分析,虽然又臭又长,但是如果想要学习Android系统源码,这是必要走的路,没有捷径。

相对于碎片学习,我更倾向于静下心来花费1个小时认真的学习一段内容。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

 

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

Android系统架构
Android是怎么启动的
Android 10.0系统启动之init进程
Android10.0系统启动之Zygote进程
Android 10.0 系统启动之SystemServer进程
Android 10.0 系统服务之ActivityMnagerService
Android10.0系统启动之Launcher(桌面)启动流程
Android10.0应用进程创建过程以及Zygote的fork流程
Android 10.0 PackageManagerService(一)工作原理及启动流程
Android 10.0 PackageManagerService(二)权限扫描
Android 10.0 PackageManagerService(三)APK扫描
Android 10.0 PackageManagerService(四)APK安装流程
《日志系统篇》

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​
《Binder通信原理》:

Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
Android10.0 Binder通信原理(二)-Binder入门篇
Android10.0 Binder通信原理(三)-ServiceManager篇
Android10.0 Binder通信原理(四)-Native-C\C++实例分析
Android10.0 Binder通信原理(五)-Binder驱动分析
Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
Android10.0 Binder通信原理(七)-Framework binder示例
Android10.0 Binder通信原理(八)-Framework层分析
Android10.0 Binder通信原理(九)-AIDL Binder示例
Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
Android10.0 Binder通信原理(十一)-Binder总结

《HwBinder通信原理》

HwBinder入门篇-Android10.0 HwBinder通信原理(一)
 HIDL详解-Android10.0 HwBinder通信原理(二)
HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
HwServiceManager篇-Android10.0 HwBinder通信原理(五)
Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
HwBinder原理总结-Android10.0 HwBinder通信原理(十一)
《编译原理》

编译系统入门篇-Android10.0编译系统(一)
编译环境初始化-Android10.0编译系统(二)
make编译过程-Android10.0编译系统(三)
Image打包流程-Android10.0编译系统(四
Kati详解-Android10.0编译系统(五)
Blueprint简介-Android10.0编译系统(六)
Blueprint代码详细分析-Android10.0编译系统(七)
Android.bp 语法浅析-Android10.0编译系统(八)
Ninja简介-Android10.0编译系统(九)
Ninja提升编译速度的方法-Android10.0编译系统(十)
Android10.0编译系统(十一)

 

 

Android init 启动进程主要分三个阶段分析:

概述,Init如何被启动
Init进程启动的源码分析
rc语法分析
 

1.概述:
init进程是linux系统中用户空间的第一个进程,进程号为1.

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

 

init进程被赋予了很多重要工作,init进程启动主要分为两个阶段:

第一个阶段完成以下内容:

ueventd/watchdogd跳转及环境变量设置
挂载文件系统并创建目录
初始化日志输出、挂载分区设备
启用SELinux安全策略
开始第二阶段前的准备
第二个阶段完成以下内容:

初始化属性系统
执行SELinux第二阶段并恢复一些文件安全上下文
新建epoll并初始化子进程终止信号处理函数
设置其他系统属性并开启属性服务
2.架构
2.1 Init进程如何被启动?


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

kernel_init启动后,完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了。

Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接执行init这个文件。

 

2.2Init进程启动后,做了哪些事?


Init进程启动后,首先挂载文件系统、再挂载相应的分区,启动SELinux安全策略,启动属性服务,解析rc文件,并启动相应属性服务进程,初始化epoll,依次设置signal、property、keychord这3个fd可读时相对应的回调函数。进入无线循环,用来响应各个进程的变化与重建。

 

 

3.kernel启动init进程 源码分析
 

3.1  kernel_init
kernel/msm-4.19/init/main.c

kernel/msm-4.19/init/main.c
kernel_init()
  |
run_init_process(ramdisk_execute_command)  //运行可执行文件,启动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);
	}
 
	/*
	 * 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的值如果有定义就去根目录下找对应的应用程序,然后启动
		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.");
}

3.2  do_basic_setup

kernel_init_freeable()
|
do_basic_setup()
static void __init do_basic_setup(void)
{
	cpuset_init_smp();//针对SMP系统,初始化内核control group的cpuset子系统。
	usermodehelper_init();// 创建khelper单线程工作队列,用于协助新建和运行用户空间程序
	shmem_init();// 初始化共享内存
	driver_init();// 初始化设备驱动
	init_irq_proc();//创建/proc/irq目录, 并初始化系统中所有中断对应的子目录
	do_ctors();// 执行内核的构造函数
	usermodehelper_enable();// 启用usermodehelper
	do_initcalls();//遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始化,
	//之所有将函数封装到数组进行遍历,主要是为了好扩展
 
	random_int_secret_init();//初始化随机数生成池
}


4. Init 进程启动源码分析
我们主要是分析Android Q(10.0) 的init的代码。

 

涉及源码文件:

platform/system/core/init/main.cpp
platform/system/core/init/init.cpp
platform/system/core/init/ueventd.cpp
platform/system/core/init/selinux.cpp
platform/system/core/init/subcontext.cpp
platform/system/core/base/logging.cpp
platform/system/core/init/first_stage_init.cpp
platform/system/core/init/first_stage_main.cpp
platform/system/core/init/first_stage_mount.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp


4.1 Init 进程入口
前面已经通过kernel_init,启动了init进程,init进程属于一个守护进程,准确的说,它是Linux系统中用户控制的第一个进程,它的进程号为1。它的生命周期贯穿整个Linux内核运行的始终。Android中所有其它的进程共同的鼻祖均为init进程。

可以通过"adb shell ps |grep init" 的命令来查看init的进程号。

Android Q(10.0) 的init入口函数由原先的init.cpp 调整到了main.cpp,把各个阶段的操作分离开来,使代码更加简洁命令,接下来我们就从main函数开始学习。

 [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);
}


4.2 ueventd_main
代码路径:platform/system/core/init/ueventd.cpp

Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。

因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。

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

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

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

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.Poll([&uevent_handlers](const Uevent& uevent) {
        for (auto& uevent_handler : uevent_handlers) {
            uevent_handler->HandleUevent(uevent); //热启动,创建设备
        }
        return ListenerAction::kContinue;
    });
    return 0;
}


4.3 init 进程启动第一阶段
代码路径:platform\system\core\init\first_stage_init.cpp

init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略

第一阶段完成以下内容:

/* 01. 创建文件系统目录并挂载相关的文件系统 */

/* 02. 屏蔽标准的输入输出/初始化内核log系统 */

 

4.3.1 FirstStageMain

int FirstStageMain(int argc, char** argv) {
    //init crash时重启引导加载程序
    //这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    //清空文件权限
    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(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
 
    // 非特权应用不能使用Andrlid cmdline
    CHECKCALL(chmod("/proc/cmdline", 0440));
    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)));
 
    if constexpr (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));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));
 
    // 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
    // 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
 
    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
 
    //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
    SetStdioToDevNull(argv);
    //在/dev目录下挂载好 tmpfs 以及 kmsg 
    //这样就可以初始化 /kernel Log 系统,供用户打印log
    InitKernelLogging(argv);
 
    ...
 
    /* 初始化一些必须的分区
     *主要作用是去解析/proc/device-tree/firmware/android/fstab,
     * 然后得到"/system", "/vendor", "/odm"三个目录的挂载信息
     */
    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
 
    struct stat 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();
 
    static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
    uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
    setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
 
    //启动init进程,传入参数selinux_steup
    // 执行命令: /system/bin/init selinux_setup
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    execv(path, const_cast<char**>(args));
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
 
    return 1;
}


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

和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。

在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。

selinux有两种工作模式:

permissive,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志,一般eng模式用
enforcing,所有操作都会进行权限检查。一般user和user-debug模式用
不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce 文件, 0表示permissive 1表示enforcing

 

4.4.1 SetupSelinux

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

代码路径: platform\system\core\init\selinux.cpp

/*此函数初始化selinux,然后执行init以在init selinux中运行*/
int SetupSelinux(char** argv) {
       //初始化Kernel日志
    InitKernelLogging(argv);
 
       // Debug版本init crash时重启引导加载程序
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
 
    //注册回调,用来设置需要写入kmsg的selinux日志
    SelinuxSetupKernelLogging();
   
     //加载SELinux规则
    SelinuxInitialize();
 
    /*
       *我们在内核域中,希望转换到init域。在其xattrs中存储selabel的文件系统(如ext4)不需要显式restorecon,
       *但其他文件系统需要。尤其是对于ramdisk,如对于a/b设备的恢复映像,这是必需要做的一步。
       *其实就是当前在内核域中,在加载Seliux后,需要重新执行init切换到C空间的用户态
       */
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }
 
  //准备启动innit进程,传入参数second_stage
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));
 
    /*
       *执行 /system/bin/init second_stage, 进入第二阶段
       */
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
 
    return 1;
}


4.4.2 SelinuxInitialize()

 

 

/*加载selinux 规则*/
void SelinuxInitialize() {
    LOG(INFO) << "Loading SELinux policy";
    if (!LoadPolicy()) {
        LOG(FATAL) << "Unable to load SELinux policy";
    }
 
    //获取当前Kernel的工作模式
    bool kernel_enforcing = (security_getenforce() == 1);
 
    //获取工作模式的配置
    bool is_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) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
}
/*
 *加载SELinux规则
 *这里区分了两种情况,这两种情况只是区分从哪里加载安全策略文件,
 *第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,
 *第二个是从 /sepolicy 读取,他们最终都是调用selinux_android_load_policy_from_fd方法
 */
bool LoadPolicy() {
    return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}


4.5 init进程启动第二阶段
第二阶段主要内容:

创建进程会话密钥并初始化属性系统
进行SELinux第二阶段并恢复一些文件安全上下文
新建epoll并初始化子进程终止信号处理函数,详细看第五节-信号处理
启动匹配属性的服务端, 详细查看第六节-属性服务
解析init.rc等文件,建立rc文件的action 、service,启动其他进程,详细查看第七节-rc文件解析

4.5.1 SecondStageMain
 

int SecondStageMain(int argc, char** argv) {
    /* 01. 创建进程会话密钥并初始化属性系统 */
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
 
    //创建 /dev/.booting 文件,就是个标记,表示booting进行中
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
    // 初始化属性系统,并从指定文件读取属性
    property_init();
 
    /* 02. 进行SELinux第二阶段并恢复一些文件安全上下文 */
        SelinuxRestoreContext();
 
    /* 03. 新建epoll并初始化子进程终止信号处理函数 */
    Epoll epoll;
    if (auto result = epoll.Open(); !result) {
        PLOG(FATAL) << result.error();
    }
 
             InstallSignalFdHandler(&epoll);
 
    /* 04. 设置其他系统属性并开启系统属性服务*/
    StartPropertyService(&epoll);
 
           /* 05 解析init.rc等文件,建立rc文件的action 、service,启动其他进程*/
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    LoadBootScripts(am, sm);
}


代码流程详细解析:

int SecondStageMain(int argc, char** argv) {
    /* 
    *init crash时重启引导加载程序
    *这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    */
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
 
    //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
    SetStdioToDevNull(argv);
    //在/dev目录下挂载好 tmpfs 以及 kmsg 
    //这样就可以初始化 /kernel Log 系统,供用户打印log
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
 
    // 01. 创建进程会话密钥并初始化属性系统
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
 
    //创建 /dev/.booting 文件,就是个标记,表示booting进行中
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
    // 初始化属性系统,并从指定文件读取属性
    property_init();
 
    /*
     * 1.如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
     * 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数,
     */
    process_kernel_dt(); // 处理 DT属性
    process_kernel_cmdline(); // 处理命令行属性
 
    // 处理一些其他的属性
    export_kernel_boot_props();
 
    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
 
    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
 
    // See if need to load debug props to allow adb root, when the device is unlocked.
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
        load_debug_prop = "true"s == force_debuggable_env;
    }
 
    // 基于cmdline设置memcg属性
    bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false);
    if (memcg_enabled) {
       // root memory control cgroup
       mkdir("/dev/memcg", 0700);
       chown("/dev/memcg",AID_ROOT,AID_SYSTEM);
       mount("none", "/dev/memcg", "cgroup", 0, "memory");
       // app mem cgroups, used by activity manager, lmkd and zygote
       mkdir("/dev/memcg/apps/",0755);
       chown("/dev/memcg/apps/",AID_SYSTEM,AID_SYSTEM);
       mkdir("/dev/memcg/system",0550);
       chown("/dev/memcg/system",AID_SYSTEM,AID_SYSTEM);
    }
 
    // 清空这些环境变量,之前已经存到了系统属性中去了
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
    unsetenv("INIT_FORCE_DEBUGGABLE");
 
    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
 
    /*
     * 02. 进行SELinux第二阶段并恢复一些文件安全上下文 
     * 恢复相关文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建的,
     * 所以需要重新恢复上下文
     */
    SelinuxRestoreContext();
 
   /*
    * 03. 新建epoll并初始化子进程终止信号处理函数
    *  创建epoll实例,并返回epoll的文件描述符
    */
    Epoll epoll;
    if (auto result = epoll.Open(); !result) {
        PLOG(FATAL) << result.error();
    }
 
    /* 
     *主要是创建handler处理子进程终止信号,注册一个signal到epoll进行监听
     *进行子继承处理
     */
    InstallSignalFdHandler(&epoll);
 
    // 进行默认属性配置相关的工作
    property_load_boot_defaults(load_debug_prop);
    UmountDebugRamdisk();
    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
 
    /*
     *04. 设置其他系统属性并开启系统属性服务
     */
    StartPropertyService(&epoll);
    MountHandler mount_handler(&epoll);
 
    //为USB存储设置udc Contorller, sys/class/udc
    set_usb_controller();
 
    // 匹配命令和函数之间的对应关系
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
 
    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }
 
    // 初始化文件上下文
    subcontexts = InitializeSubcontexts();
 
   /*
     *05 解析init.rc等文件,建立rc文件的action 、service,启动其他进程
     */
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
 
    LoadBootScripts(am, sm);
 
    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();
 
    // 当GSI脚本running时,确保GSI状态可用.
    if (android::gsi::IsGsiRunning()) {
        property_set("ro.gsid.image_running", "1");
    } else {
        property_set("ro.gsid.image_running", "0");
    }
 
 
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
 
    // 执行rc文件中触发器为 on early-init 的语句
    am.QueueEventTrigger("early-init");
 
    // 等冷插拔设备初始化完成
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
 
    // 开始查询来自 /dev的 action
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
 
    // 设备组合键的初始化操作
    Keychords keychords;
    am.QueueBuiltinAction(
        [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
            for (const auto& svc : ServiceList::GetInstance()) {
                keychords.Register(svc->keycodes());
            }
            keychords.Start(&epoll, HandleKeychord);
            return Success();
        },
        "KeychordInit");
 
    //在屏幕上显示Android 静态LOGO
    am.QueueBuiltinAction(console_init_action, "console_init");
 
    // 执行rc文件中触发器为on init的语句
    am.QueueEventTrigger("init");
 
    // Starting the BoringSSL self test, for NIAP certification compliance.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
 
    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
 
    // Initialize binder before bringing up other system services
    am.QueueBuiltinAction(InitBinder, "InitBinder");
 
    // 当设备处于充电模式时,不需要mount文件系统或者启动系统服务
    // 充电模式下,将charger假如执行队列,否则把late-init假如执行队列
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
 
    // 基于属性当前状态 运行所有的属性触发器.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
 
    while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
 
        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }
 
//依次执行每个action中携带command对应的执行函数
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                auto next_process_action_time = HandleProcessActions();
 
                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_action_time) {
                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                            *next_process_action_time - boot_clock::now());
                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                }
            }
 
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
 
        // 循环等待事件发生
        if (auto result = epoll.Wait(epoll_timeout); !result) {
            LOG(ERROR) << result.error();
        }
    }
 
    return 0;
}

5. 信号处理
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

子进程重启流程如下图所示:

信号处理主要工作:

初始化信号signal句柄
循环处理子进程
注册epoll句柄
处理子进程终止
注:  EPOLL类似于POLL,是Linux中用来做事件触发的,跟EventBus功能差不多。linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势

 

5.1 InstallSignalFdHandler
在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出,了解这些背景后,我们来看看init进程如何处理这个信号。

首先,新建一个sigaction结构体,sa_handler是信号处理函数,指向内核指定的函数指针SIG_DFL和Android 9.0及之前的版本不同,这里不再通过socket的读写句柄进行接收信号,改成了内核的信号处理函数SIG_DFL。
然后,sigaction(SIGCHLD, &act, nullptr) 这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理
最后,RegisterHandler 的作用就是signal_read_fd(之前的s[1])收到信号,触发handle_signal
终上所述,InstallSignalFdHandler函数的作用就是,接收到SIGCHLD信号时触发HandleSignalFd进行信号处理

                                   信号处理示意图:

 

代码路径:platform/system/core/init.cpp

说明:该函数主要的作用是初始化子进程终止信号处理过程

static void InstallSignalFdHandler(Epoll* epoll) {
 
    // SA_NOCLDSTOP使init进程只有在其子进程终止时才会受到SIGCHLD信号
    const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
    sigaction(SIGCHLD, &act, nullptr);
 
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD); 
 
    if (!IsRebootCapable()) {
        // 如果init不具有 CAP_SYS_BOOT的能力,则它此时正值容器中运行
        // 在这种场景下,接收SIGTERM 将会导致系统关闭
        sigaddset(&mask, SIGTERM);
    }
 
    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
        PLOG(FATAL) << "failed to block signals";
    }
 
    // 注册处理程序以解除对子进程中的信号的阻止
    const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
    if (result != 0) {
        LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
    }
 
    //创建信号句柄
    signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
    if (signal_fd == -1) {
        PLOG(FATAL) << "failed to create signalfd";
    }
 
    //信号注册,当signal_fd收到信号时,触发HandleSignalFd
    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) {
        LOG(FATAL) << result.error();
    }
}


5.2 RegisterHandler
 

代码路径:/platform/system/core/epoll.cpp

说明:信号注册,把fd句柄加入到 epoll_fd_的监听队列中

Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {
    if (!events) {
        return Error() << "Must specify events";
    }
    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
    if (!inserted) {
        return Error() << "Cannot specify two epoll handlers for a given FD";
    }
    epoll_event ev;
    ev.events = events;
    // std::map's iterators do not get invalidated until erased, so we use the
    // pointer to the std::function in the map directly for epoll_ctl.
    ev.data.ptr = reinterpret_cast<void*>(&it->second);
    // 将fd的可读事件加入到epoll_fd_的监听队列中
    if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {
        Result<void> result = ErrnoError() << "epoll_ctl failed to add fd";
        epoll_handlers_.erase(fd);
        return result;
    }
    return {};
}

5.3 HandleSignalFd
 

代码路径:platform/system/core/init.cpp

说明:监控SIGCHLD信号,调用 ReapAnyOutstandingChildren 来 终止出现问题的子进程

static void HandleSignalFd() {
    signalfd_siginfo siginfo;
    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
    if (bytes_read != sizeof(siginfo)) {
        PLOG(ERROR) << "Failed to read siginfo from signal_fd";
        return;
    }
 
   //监控SIGCHLD信号
    switch (siginfo.ssi_signo) {
        case SIGCHLD:
            ReapAnyOutstandingChildren();
            break;
        case SIGTERM:
            HandleSigtermSignal(siginfo);
            break;
        default:
            PLOG(ERROR) << "signal_fd: received unexpected signal " << siginfo.ssi_signo;
            break;
    }
}


 

5.4 ReapOneProcess
 

代码路径:/platform/system/core/sigchld_handle.cpp

说明:ReapOneProcess是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,

最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程

void ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}
 
static bool ReapOneProcess() {
    siginfo_t siginfo = {};
    //用waitpid函数获取状态发生变化的子进程pid
    //waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
        PLOG(ERROR) << "waitid failed";
        return false;
    }
 
    auto pid = siginfo.si_pid;
    if (pid == 0) return false;
 
    // 当我们知道当前有一个僵尸pid,我们使用scopeguard来清楚该pid
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
 
    std::string name;
    std::string wait_string;
    Service* service = nullptr;
 
    if (SubcontextChildReap(pid)) {
        name = "Subcontext";
    } else {
        //通过pid找到对应的service
        service = ServiceList::GetInstance().FindService(pid, &Service::pid);
 
        if (service) {
            name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
            if (service->flags() & SVC_EXEC) {
                auto exec_duration = boot_clock::now() - service->time_started();
                auto exec_duration_ms =
                    std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
                wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
            } else if (service->flags() & SVC_ONESHOT) {
                auto exec_duration = boot_clock::now() - service->time_started();
                auto exec_duration_ms =
                        std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration)
                                .count();
                wait_string = StringPrintf(" oneshot service took %f seconds in background",exec_duration_ms / 1000.0f);
            }
        } else {
            name = StringPrintf("Untracked pid %d", pid);
        }
    }
 
    if (siginfo.si_code == CLD_EXITED) {
        LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
    } else {
        LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
    }
 
    //没有找到service,说明已经结束了,退出
    if (!service) return true;
 
    service->Reap(siginfo);//清除子进程相关的资源
 
    if (service->flags() & SVC_TEMPORARY) {
        ServiceList::GetInstance().RemoveService(*service); //移除该service
    }
 
    return true;
}


 

6.属性服务
我们在开发和调试过程中看到通过property_set可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性,

Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看具体的流程是什么

 

6.1 property_init
代码路径:platform/system/core/property_service.cpp

说明:初始化属性系统,并从指定文件读取属性,并进行SELinux注册,进行属性权限控制

清除缓存,这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为 /dev/_properties_

然后就是调用CreateSerializedPropertyInfo加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存

void property_init() {
 
    //设置SELinux回调,进行权限控制
    selinux_callback cb;
    cb.func_audit = PropertyAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
 
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}

通过CreateSerializedPropertyInfo 来加载以下目录的contexts:

1)与SELinux相关

/system/etc/selinux/plat_property_contexts
 
/vendor/etc/selinux/vendor_property_contexts
 
/vendor/etc/selinux/nonplat_property_contexts
 
/product/etc/selinux/product_property_contexts
 
/odm/etc/selinux/odm_property_contexts


2)与SELinux无关

/plat_property_contexts
 
/vendor_property_contexts
 
/nonplat_property_contexts
 
/product_property_contexts
 
/odm_property_contexts


 

6.2 StartPropertyService
代码路径: platform/system/core/init.cpp

说明:启动属性服务

首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性,

最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd

void StartPropertyService(Epoll* epoll) {
    property_set("ro.property_service.version", "2");
 
    //建立socket连接
    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, {})) {
        property_set_fd = *result;
    } else {
        PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
    }
 
    // 最大监听8个并发
    listen(property_set_fd, 8);
 
    // 注册property_set_fd,当收到句柄改变时,通过handle_property_set_fd来处理
    if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
        PLOG(FATAL) << result.error();
    }
}


6.3 handle_property_set_fd
代码路径:platform/system/core/property_service.cpp

说明:建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用HandlePropertySet做具体的操作

HandlePropertySet是最终的处理函数,以"ctl"开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作

 

static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
 
    // 等待客户端连接
    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    if (s == -1) {
        return;
    }
 
    ucred cr;
    socklen_t cr_size = sizeof(cr);
    // 获取连接到此socket的进程的凭据
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }
 
    // 建立socket连接
    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;
 
    uint32_t cmd = 0;
    // 读取socket中的操作信息
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }
 
    // 根据操作信息,执行对应处理,两者区别一个是以char形式读取,一个以String形式读取
    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];
 
        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }
 
        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;
 
        std::string source_context;
        if (!socket.GetSourceContext(&source_context)) {
            PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed";
            return;
        }
 
        const auto& cr = socket.cred();
        std::string error;
        uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid
                       << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
        }
 
        break;
      }
 
    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }
 
        std::string source_context;
        if (!socket.GetSourceContext(&source_context)) {
            PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed";
            socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
            return;
        }
 
        const auto& cr = socket.cred();
        std::string error;
        uint32_t result = HandlePropertySet(name, value, source_context, cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid
                       << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
        }
        socket.SendUint32(result);
        break;
      }
 
    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}


7.第三阶段init.rc
当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。但是init进程如何其他其他进程呢?其他进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/init second_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。

在这个基础上Android推出了一个init.rc的机制,即类似通过读取配置文件的方式,来启动不同的进程。接下来我们就来看看init.rc是如何工作的。

init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。

init.rc在手机的目录:./init.rc

init.rc主要包含五种类型语句:

Action
Command
Service
Option
Import

7.1 Action
动作表示了一组命令(commands)组成.动作包括一个触发器,决定了何时运行这个动作

Action: 通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:

on early-init; 在初始化早期阶段触发;
on init; 在初始化阶段触发;
on late-init; 在初始化晚期阶段触发;
on boot/charger: 当系统启动/充电时触发;
on property:<key>=<value>: 当属性值满足条件时触发;
 

7.2 Command
command是action的命令列表中的命令,或者是service中的选项 onrestart 的参数命令,命令将在所属事件发生时被一个个地执行.

下面列举常用的命令

class_start <service_class_name>: 启动属于同一个class的所有服务;
class_stop <service_class_name> : 停止指定类的服务
start <service_name>: 启动指定的服务,若已启动则跳过;
stop <service_name>: 停止正在运行的服务
setprop <name> <value>:设置属性值
mkdir <path>:创建指定目录
symlink <target> <sym_link>: 创建连接到<target>的<sym_link>符号链接;
write <path> <string>: 向文件path中写入字符串;
exec: fork并执行,会阻塞init进程直到程序完毕;
exprot <name> <name>:设定环境变量;
loglevel <level>:设置log级别
hostname <name> : 设置主机名
import <filename> :导入一个额外的init配置文件
 

7.3 Service
服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。

命令:service <name><pathname> [ <argument> ]* <option> <option>

 

参数

含义

<name>

表示此服务的名称

<pathname>

此服务所在路径因为是可执行文件,所以一定有存储路径。

<argument>

启动服务所带的参数

<option>

对此服务的约束选项

init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。

例如: service servicemanager /system/bin/servicemanager代表的是服务名为servicemanager,服务执行的路径为/system/bin/servicemanager。

 

 

7.4 Options
Options是Service的可选项,与service配合使用

disabled: 不随class自动启动,只有根据service名才启动;
oneshot: service退出后不再重启;
user/group: 设置执行服务的用户/用户组,默认都是root;
class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;
onrestart:当服务重启时执行相应命令;
socket: 创建名为/dev/socket/<name>的socket
critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式
default: 意味着disabled=false,oneshot=false,critical=false。

 

7.5 import
用来导入其他的rc文件

命令:import <filename>

 

7.6 init.rc 解析过程
7.6.1 LoadBootScripts

代码路径:platform\system\core\init\init.cpp

说明:如果没有特殊配置ro.boot.init_rc,则解析./init.rc

把/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,

/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件

 

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);
 
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/product_services/etc/init")) {
            late_import_paths.emplace_back("/product_services/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

Android7.0后,init.rc进行了拆分,每个服务都有自己的rc文件,他们基本上都被加载到/system/etc/init,/vendor/etc/init, /odm/etc/init等目录,等init.rc解析完成后,会来解析这些目录中的rc文件,用来执行相关的动作。

 

代码路径:platform\system\core\init\init.cpp

说明:创建Parser解析对象,例如service、on、import对象

 

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;
 
    parser.AddSectionParser(
            "service", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
 
    return parser;
}


7.6.2 执行Action动作

按顺序把相关Action加入触发器队列,按顺序为 early-init -> init -> late-init. 然后在循环中,执行所有触发器队列中Action带Command的执行函数。

 

am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) {
if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
}


7.6.2 Zygote启动

从Android 5.0的版本开始,Android支持64位的编译,因此zygote本身也支持32位和64位。通过属性ro.zygote来控制不同版本的zygote进程启动。

在init.rc的import段我们看到如下代码:

import /init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件

 init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。

分别是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬件决定调用哪个文件。

这里拿64位处理器为例,init.zygote64.rc的代码如下所示:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main        # class是一个option,指定zygote服务的类型为main
            priority -20
            user root
           group root readproc reserved_disk
            socket zygote stream 660 root system  # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
           socket usap_pool_primary stream 660 root system
            onrestart write /sys/android_power/request_state wake # onrestart是一个option,说明在zygote重启时需要执行的command
            onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:

service zygote :init.zygote64.rc 中定义了一个zygote服务。 init进程就是通过这个service名称来创建zygote进程

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:

zygote这个服务,通过执行进行/system/bin/app_process64 并传入4个参数进行运行:

参数1:-Xzygote 该参数将作为虚拟机启动时所需的参数
参数2:/system/bin 代表虚拟机程序所在目录
参数3:--zygote 指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
参数4:--start-system-server 告诉Zygote进程启动systemServer进程
8.总结
init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。

init进程第二阶段主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些功能落实。

init进行第三阶段主要是解析init.rc 来启动其他进程,进入无限循环,进行子进程实时监控。

 

  • 14
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
课程简述  Android是目前最为流行的移动操作系统之一,它的开发涉及到多个知识领域。本课程将深入介绍Android系统启动过程中的重要组成部分——init进程,并探讨与之相关的多项关键技术。我们还将提供实际的开发案例,以RK3399开发板为例,通过演示Android产品配置、init启动流程、selinux权限管理、init.rc启动脚本定制等实际案例,让学员深入理解这些技术在实际产品开发中的应用场景和实现方法,提高学员的实际开发能力和经验,从而更好地应对实际产品开发中遇到的问题和挑战。知识运用方向学习Android启动方面的知识,可以参与如下实际开发工作项:启动流程定制: 根据产品需求调整Android启动流程,包括修改init.rc脚本、修改启动顺序和等待时间、加入自定义服务等。属性系统定制: 通过Android属性系统定制化启动流程,例如增加产品版本信息、定制开机音量等。日志系统分析: 掌握日志的捕捉、分析和排错技术,在启动过程中,需要加入调试信息来方便开发人员进行调试,同时需要进行日志的优化,避免日志输出过多占用过多的系统资源。selinux安全策略定制:在Android系统中,selinux是一种安全机制,用于保护系统的敏感资源和数据。在实际开发中,可能需要对selinux策略进行定制,以确保系统的安全性和稳定性。课程内容主要内容简述1, RK3399 开发板操作这部分内容重点介绍如何在FIreFly开发板上将Android 10系统运行起来, 包含编译FireFly的Android源码下载和编译, 镜像烧录运行,内核和模块编译,以及RK3399内核启动init进程的过程。2, 产品定制这部分讲解获取到方案商或者原厂提供的源码后, 如何定制一个新的产品,产品配置文件和模型, 原始代码中的配置文件和定制化东西3, Android日志代码编写之前讲过Android的日志系统, 并没涉及到代码编写, 这个部分重点讲解C/C++, java代码编写日志的API和代码4, 属性系统在Android中,属性使用的非常频繁的,可以用来作为进程间通信,也可以用于一些行为控制, 这个部分会重点介绍属性系统框架, API接口, 属性文件等知识点5,selinux进程对文件进行访问时,Android 4.3就开始集成了selinux权限管控, 如果需要启动某个脚本或者服务, selinux的配置就避免不了,并且Android8之后, Android系统对进程访问的权限管控的非常严格。6, init.rc脚本Android定义的一种脚本, 改脚本是有init进程启动, 是非常重要的一个脚本, 会包含系统中的其他很多脚本, 在我们系统开发时, 我们经常通过这个脚本进行一些定制化动作。7, init进程代码分析想要了解一个系统,就必须对源码进行分析和理解, 这个章节,带大家去跟读init进程代码, 这样,换了另外一个Android版本,完全就可以去读代码, 知道有什么变化。 
### 回答1: android 10.0系统应用默认授权是指在android 10.0系统中,部分应用在安装后默认会被授予一些权限,而不需要用户在应用启动后再去手动开启这些权限,这样可以提高用户的使用体验。 android 10.0系统应用默认授权的目的是为了加强应用的安全性,提高用户的隐私保护。应用需要访问某些敏感数据或功能时,用户在使用前需要手动开启权限,这样可以减少恶意应用通过获取用户授权的方式获取用户的私人信息。 在android 10.0系统中,应用默认被授权的权限包括日历、相机、联系人、位置、麦克风、电话、短信、存储空间等。这些权限是应用正常运行所必需的,用户可以在应用管理器中查看和管理应用授权的权限。 尽管android 10.0系统应用默认授权提高了用户的使用体验,但也存在一些风险。如果用户使用的是恶意应用,这些应用也可以默认获取一些敏感权限,从而获取用户的私人信息。因此,用户需要保持对应用的警惕,并仔细审核应用的权限请求。 ### 回答2: 在Android 10.0系统中,应用默认授权是一种新的权限模型。它改变了以往Android系统中权限管理的方式,使用户对应用程序的权限管理更加方便和安全。 传统的权限管理模型通常是一次性处理权限请求,无法区分应用程序对某一项权限的使用情况。这种方式缺少细节和灵活性,可能导致应用程序通过某些权限去访问用户的隐私信息。而Android 10.0系统应用默认授权则可以保护用户的隐私信息和数据安全。 应用默认授权模型允许应用程序在不请求用户手动授权的情况下,自动获得某些权限,并在必要的时候再向用户请求授权。这种方式可以减少用户被安装并包含恶意代码的应用程序所利用的风险。 具体地说,当应用程序请求任何运行时权限时,Android 10.0系统将无法立即授予权限。相反,它将显示一个对话框,询问用户是否要授予权限。只有在用户同意授予权限时,应用程序才能获得此权限。 此外,应用默认授权模型还能够自动限制应用程序对某些权限的访问,比如位置信息和网络数据,除非用户主动授予权限或应用程序已经获得了相应的批准。 总之,Android 10.0系统的应用默认授权能够保护用户隐私,增加应用程序的灵活性,并减少用户遭受来自恶意应用程序的风险。 ### 回答3: Android 10.0系统的应用默认授权,是指所有应用程序在安装时系统会默认授予其部分权限,而不是像以前版本的系统一样需要用户在应用使用时手动授权。 Android 10.0系统应用默认授权的目的,是为了提高应用程序的用户便利性和操作流畅性,同时还可以降低用户在使用应用程序时需要授权的次数。但是这也有可能导致一些应用程序获取用户隐私的风险。 在Android 10.0系统中,应用程序默认被授权的权限包括:网络访问、用户日历、联系人和传感器等。对于其他一些权限,如摄像头、麦克风、存储空间和定位等信息,用户在应用程序使用时需要手动授权才能访问。 因此,用户在使用Android 10.0系统时需要非常谨慎地选择哪些应用程序可以获得自己的隐私权限。建议用户在安装应用程序前,仔细检查其权限请求,尽可能减少不必要的隐私授权操作。 同时,为了更好地保护用户的隐私,Android 10.0系统也提供了更加严格的权限管理功能。用户可以通过设置菜单中的 “应用程序和通知”-“应用程序权限” 来查看和修改应用程序的权限,在此基础上更好地控制应用程序的使用和权限访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值