基于Android13的系统启动流程分析(三)之FirstStageMain阶段

Android13的启动过程中,FirstStageMain阶段主要任务是初始化基本文件系统,如tmpfs、devpts、sysfs等,挂载/dev、/proc等,创建设备节点,并处理selinux和log。这一阶段还涉及到内核模块加载、设备权限设置以及日志系统的初始化。此外,FirstStageMain会创建/first_stage_ramdisk作为新的根目录,确保系统安全性和分区校验。

Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲FirstStageMain阶段
(若分析有误敬请指教)

本章讲解的方向和你将收获的知识:

  1. 用户空间进程的调用流程
  2. 当进程挂掉后该如何处理
  3. 何时挂载上的基本文件系统和文件系统小知识
  4. FirstStageMain阶段会挂载上什么分区,会创建哪些设备节点

一. Android系统启动基本介绍

init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init

1. Bootloader 引导

当按下设备电源键时,最先运行的就是 bootloader(固化在ROM的程序),bootloader 的主要作用就是硬件设备(如 CPU、flash、内存)的初始化并加载到RAM,通过建立内存空间映射,为装载 Linux 内核做好准备,。如果 bootloader 在运行期间,按下预定义的组合按键,可以进入系统fastboot模式 或者 Receiver 模式。

2. 装载和启动 Linux 内核

在编译完AOSP时会生成boot.img或者boot_debug.img,该镜像就是 Linux 内核和根文件系统,bootloader 会把该镜像装载到内存中,然后 linux 内核会执行整个系统的初始化,完成后装载根文件系统,最后启动 init 进程。

3. 启动 Init 进程

Linux 内核加载完毕后,会从kernel内核层调用到用户空间层,则会首先启动 init 进程,init 进程是系统的第一个进程,在 init 进程的启动过程中会解析最主要的 init.rc 脚本,根据 init.rc 文件的描述,系统会创建文件及目录以及权限的赋予,初始化属性和启动 Android 系统重要的守护进程

4. 启动 ServiceManager

ServiceManager 由 init 进程启动。它的主要作用是管理 Binder 服务,service 服务的注册和查找,如 AMS、PMS, 都是通过 ServiceManger 来管理。

5. 启动 MediaServer

MediaServer 是由 init 进程启动,它包含了一些多媒体 binder 服务,包括 CameraService、MediaPlayerService、AudioPolicyService 等等

onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond

6. 启动 Zygote 进程

init 进程初始化结束后,会启动 Zygote 进程。在 Android 系统中所有的应用程序进程和系统服务进程都是通过Zygote 进程 fork 出来的。预装载系统的资源文件,所有从 Zygote 进程 fork 出的子进程都会共享这些资源,节省了资源加载的时间,提高的应用的启动速度。Zygote 启动结束后也会变为守护进程,负责响应启动 APK 的请求。

7. 启动 SystemServer

SystemServer 是跟随Zygote创建出来的第一个子进程,同时也是整个 Android 系统的核心。在系统中运行的大部分系统服务都是有 SystemServer 创建,接着会启动 AMS、WMS、PMS 等。阅读过源码可以发现大部分服务会继承自systemServer

PD2183:/ $ ps --pid 1                                                                                                                                                             
USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME                       
root             1     0 13133644  8264 0                   0 S init

init进程由init_task进程fork而来,在kernel初始化完成后init_task便化身为idle进程,可以看到init的进程pid为:1
而init_task进程是Linux中第一个进程,也即0号进程(PID为0),这里不进一步分析linux内核层

二. FirstStageMain阶段分析

镜像 内容
ramdisk.img 系统root根目录,挂载点 /
boot.img 包含了kernel + ramdisk.img
super.img 在Android R版本开始引入的动态分区结构,目的是为了解决system和vender等分区size不能动态调整的问题
super.img 包含了system_a(b).img,vendor_a(b).img,product_a(b).img,拿到super.img可以进行解包拿到子镜像
userdata.img 挂载点/data,包含了用户的所有数据,例如应用数据等

针对super分区,在开机init的first stage第一阶段运行期间,会解析并验证metadata元数据并创建虚拟block设备来表示每个子分区,super分区头部信息就是metadata元数据,用于映射(mapping)出虚拟block设备,这里盗用一张大佬的图来看一下super结构

super镜像super分区除了包含system\product\vendor,在头部信息包含了描述分区布局的metadata,系统加载动态分区时读取metadata,对其进行解析。

Metadata中的信息会被转换成device mapper中的映射表Mapping Table,基于这个映射表,super分区对应设备/dev/block/by-name/super的不同部分被映射成多个虚拟设备,如/dev/block/mapper/system_a/dev/block/mapper/vendor_a

1. 用户空间层main.cpp

using namespace android::init;

int main(int argc/*1*/, char** argv/*init*/) {
   
    // 内核层传过来的,argc:1,argv:init
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)
    __hwasan_set_error_report_callback(AsanReportCallback);
#endif
    // Boost prio which will be restored later
    setpriority(PRIO_PROCESS, 0, -20); // 设置进程最高优先级 -20最高,20最低
    if (!strcmp(basename(argv[0]), "ueventd")) {
   
   
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
   
   
        if (!strcmp(argv[1], "subcontext")) {
   
   
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
   
   
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
   
   
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv); //第一阶段执行
}

内核层传过来的参数:argc:1,argv:init,所以第一阶段仍然是调用return FirstStageMain(argc, argv); 这里不详细介绍subcontext,ueventd,只关注FirstStageMain阶段即可,调用顺序如下

2. FirstStageMain(int argc, char** argv)解析

该阶段所挂载的文件系统都属于ramdisk,运行在虚拟内存上

int FirstStageMain(int argc, char** argv) {
   
   
    // init阶段的启动引导加载程序(bootLoader),若发生异常重启也会再次执行,主要处理init || fork的子进程进程异常行为
    // init信号处理器,调试版本当init crash,默认重启到 bootLoader
    if (REBOOT_BOOTLOADER_ON_PANIC) {
   
   
        InstallRebootSignalHandlers();// setup1
    }
    // 用来设置创建目录或文件时所应该赋予权限的掩码
    // Linux中,文件默认最大权限是666,目录最大权限是777,当创建目录时,假设掩码为022,那赋予它的权限为(777 & ~022)= 755
    // 在执行init第一阶段时,先执行umask(0),使创建的目录或文件的默认权限为最高
    umask(0);

    // 第一次执行时清除环境变量,reset path
    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    // setup 2
    // 设置linux最基本的文件系统并且挂载到 / 目录(init ram disk)上,
    // 并给0755权限(即用户具有读/写/执行权限,组用户和其它用户具有读写权限),后续会通过rc文件处理一些分区权限和进程
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); //将/dev设置为tmpfs并挂载,设置0755权限,tmpfs是在内存上建立的文件系统(Filesystem)
    CHECKCALL(mkdir("/dev/pts", 0755));//tmpfs文件系统类型
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mkdir("/dev/dm-user", 0755));//tmpfs文件系统类型
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
    // setup 3
 	CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
    // 修改 「保存操作系统的启动参数」 的权限:0440,
    // 修改权限的目的是为了 不要将原始bootConfig暴露给非特权进程,部分文件系统只能是0440权限,如果修改权限则无法读取和操作
    // /proc/cmdline中保存bootloader 启动linux kernel 时 的参数
    CHECKCALL(chmod("/proc/cmdline", 0440));
    std::string cmdline;
    // 读取操作系统的启动参数
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    // 修改权限的目的是为了 不要将原始bootConfig暴露给非特权进程
    // 部分文件系统只能是0440权限,如果修改权限则无法读取和操作
    chmod("/proc/bootconfig", 0440);
    std::string bootconfig;
    // 读取系统启动参数配置
    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
    gid_t groups[] = {
   
   AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    // setup 4
    // 挂载/sys内核,并设置为sysfs文件系统类型,sysfs是一个伪文件系统。
    // 不代表真实的物理设备,在linux内核中,sysfs文件系统将长期存在于RAM中
    // sysfs文件系统将每个设备抽象成文件,挂载sysfs文件系统在sys目录,用来访问内核信息
    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)));
    }
    // 文件系统:/dev/random和 /dev/urandom是 Linux 上的字符设备文件,它们是随机数生成器,为系统提供随机数
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
    
    // 创建日志系统的串口log(伪终端),这是日志包装器所需要的,它在ueventd运行之前被调用。
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
    
    // setup 5
    // 重要文件系统及分区在第一阶段挂载,其他可以在rc执行流程中挂载
    // 挂载/mnt/{vendor,product},这些相对比较重要,所以只挂载重要的,其余的动作会在第二阶段的解析rc文件中处理
    // tmpfs之前说过了,是运作在RAM的文件系统
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值