Android 12 S 系统开机流程分析-FirstStageMain(一)

news2024/7/2 3:59:51

开机有好几种方式启动,本文主要讲的是按Power键开机流程。

本文参考AOSP 12原生代码,链接为:AOSP 12 Searchicon-default.png?t=N7T8http://aospxref.com/android-12.0.0_r3/

目录

1. BootLoader加载

2. kernel启动

3. init进程启动

3.1 FirstStageMain

3.1.1 init若崩溃会重新启动BootLoader

3.1.2 创建以及挂载文件系统

3.1.3 kernel log初始化

3.1.4 加载/lib/moudles/下的ko文件, 打开串口log

3.1.5 拷贝ramdisk prop文件,创建new ramdisk, 删除old ramdisk

3.1.6 重新执行init进程并携带参数selinux_setup


1. BootLoader加载

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

1)当用户按下开机键时,引导芯片代码开始从预定义的地方(固定在ROM中)开始执行,加载BootLoader到内存中执行。

2)BootLoader是在操作系统内核运行之前运行的一段小程序,通过这段小程序初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境,最终目标是将系统OS拉起并运行。

3)整个系统的加载任务都是由BootLoader完成的。

2. kernel启动

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

1)当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。

2)在内核完成系统设置后,它首先在系统文件中寻找init.rc,并启动init进程

kernel会去启动init,启动log如下

01-05 23:45:42.396     1     1 I         : Run /init as init process
01-05 23:45:42.396     1     1 D with arguments:  
01-05 23:45:42.396     1     1 D         : /init
01-05 23:45:42.396     1     1 D with environment:  
01-05 23:45:42.396     1     1 D         : HOME=/
01-05 23:45:42.396     1     1 D         : TERM=linux

3. init进程启动

init是被kernel拉起来的。

init进程是Android系统启动后,由内核启动的第一个用户级进程,init的进程号为1.

Android中所有进程都是由init进程创建并运行的。

init启动主要有三大步骤,分别为

    1. FirstStageMain(第一阶段):主要挂载基本的文件系统,挂载特定分区等

    2. SetupSelinux(Selinux配置阶段)

    3. SecondStageMain(第二阶段)

如果抓一份开机的log,会发现有如下log输出。分别对应各个步骤的log,且可以看到pid为1。

01-05 23:45:42.397     1     1 I init    : init first stage started!
01-05 23:45:42.812     1     1 I init    : Opening SELinux policy
01-05 23:45:42.819     1     1 I init    : Loading SELinux policy
01-05 23:45:42.943     1     1 I init    : init second stage started!

init相关代码目录为:

/system/core/init

init进程启动后,首先会走它的main方法,首先会设置init进程优先级为最大,再根据携带的参数决定走哪个方法,由于kernel启动的时候没有携带参数,所以argc是1。

代码如下

/system/core/init/main.cpp
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
//setpriority(int which, int who, int prio);
//prio范围为:-20~20,prio越小,优先级越大。
//which为PRIO_PROCESS时,含义为设置进程号为who的优先级为prio
//设置init进程的优先级为最高优先级
    setpriority(PRIO_PROCESS, 0, -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);
        }
    }
//由于kernel启动init的时候没有携带任何参数,所以会首先走入此方法
    return FirstStageMain(argc, argv);
}

3.1 FirstStageMain

接着讲FirstStageMain函数具体做了什么。

由于执行的步骤太多,下面一步步来分析具体做了什么

3.1.1 init若崩溃会重新启动BootLoader

当init崩溃时,为了防止内核进入panic状态,会重新启动BootLoader。

int FirstStageMain(int argc, char** argv) {
//1
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

1)InstallRebootSignalHandlers

InstallRebootSignalHandlers中主要做了以下几件事:

1. 初始化了一个自定义信号集,将其所有信号都填充满,即将信号集中的所有的标志位都置为1,使得这个集合包含所有可接受的信号,也就是阻塞所有信号。这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。

2. init创建出来的子进程不做处理,直接exit;如果不是子进程,则代表是init进程,则执行InitFatalReboot

3. 通过syscall向内核发送重启命令

4. 捕获一些信号

void InstallRebootSignalHandlers() {
    struct sigaction action;
    memset(&action, 0, sizeof(action));
//用于初始化一个自定义信号集,将其所有信号都填充满,也就是将信号集中的所有的
//标志位置为1,使得这个集合包含所有可接受的信号
//这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。
    sigfillset(&action.sa_mask);
    action.sa_handler = [](int signal) {
//从init派生的进程也会捕获这些信号处理程序,但是我们不希望它们触发重新启动,
//所以我们在这里直接为子进程调用_exit()。
        if (getpid() != 1) {
            _exit(signal);
        }

//调用DoReboot()或LOG(FATAL)不是一个好的选择,因为这是一个信号处理程序。
//RebootSystem使用syscall()
        InitFatalReboot(signal);
    };
    action.sa_flags = SA_RESTART;
//sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)
//参数1:要捕获的信号
//参数2:接收到信号之后对信号进行处理的结构体
//参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL
    sigaction(SIGABRT, &action, nullptr);
    sigaction(SIGBUS, &action, nullptr);
    sigaction(SIGFPE, &action, nullptr);
    sigaction(SIGILL, &action, nullptr);
    sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
    sigaction(SIGSTKFLT, &action, nullptr);
#endif
    sigaction(SIGSYS, &action, nullptr);
    sigaction(SIGTRAP, &action, nullptr);
}

2)InitFatalReboot

InitFatalReboot做了以下几件事:

1. 判断init是否可以创建子进程,如果不能创建,则直接重启;如果能创建,则sleep 5ms,再重启子进程。

2. 如果是init进程,则获取异常的backtrace。

3. 如果init进程被标记为异常了,则往/proc/sysrq-trigger写c,让系统陷入崩溃,然后直接退出。

echo b > /proc/sysrq-trigger    立即重启
echo o > /proc/sysrq-trigger    立即关机
echo c > /proc/sysrq-trigger    立即让系统崩溃
echo t > /proc/sysrq-trigger    导出线程状态信息
echo u > /proc/sysrq-trigger    立即重新挂载所有的文件系统为只读

4.如果init没有被标记为异常,则重启init进程。

void __attribute__((noreturn)) InitFatalReboot(int signal_number) {
    auto pid = fork();//创建子进程,成功0,失败-1

    if (pid == -1) {
        //不能创建子进程,不用尝试获取backtrace,直接重启
        RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
    } else if (pid == 0) {
        // 可以创建子进程,说明当前在子进程上,并且子进程需确保能重启
        sleep(5);
        //子进程重启
        RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
    }

   //尝试获取init的backtrace再关机重启
    LOG(ERROR) << __FUNCTION__ << ": signal " << signal_number;
    std::unique_ptr<Backtrace> backtrace(
            Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
    if (!backtrace->Unwind(0)) {
        LOG(ERROR) << __FUNCTION__ << ": Failed to unwind callstack.";
    }
    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
        LOG(ERROR) << backtrace->FormatFrameData(i);
    }
//判断init是否被标记为异常
//异常时:/proc/cmdline中有节点androidboot.init_fatal_panic,并且其值为true
    if (init_fatal_panic) {
        LOG(ERROR) << __FUNCTION__ << ": Trigger crash";
//往/proc/sysrq-trigger写c
        android::base::WriteStringToFile("c", PROC_SYSRQ);
        LOG(ERROR) << __FUNCTION__ << ": Sys-Rq failed to crash the system; fallback to exit().";
        _exit(signal_number);
    }
//init重启
    RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
}

3)RebootSystem

    走到这个函数,说明就要发送重启命令了,是通过syscall去发送的。

    在用户空间和内核空间之间,通过syscall(系统调用, system call)的中间层来通信,连接用户态和内核态的桥梁。用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。

RebootSystem如下:

void __attribute__((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
    LOG(INFO) << "Reboot ending, jumping to kernel";

    if (!IsRebootCapable()) {
        // On systems where init does not have the capability of rebooting the
        // device, just exit cleanly.
        exit(0);
    }

    switch (cmd) {
        case ANDROID_RB_POWEROFF:
            reboot(RB_POWER_OFF);
            break;
//前面传递的cmd是ANDROID_RB_RESTART2,发送重启命令
//Syscall是连接用户态和内核态的桥梁。
//用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作
        case ANDROID_RB_RESTART2:
            syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                    LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());
            break;

        case ANDROID_RB_THERMOFF:
            if (android::base::GetBoolProperty("ro.thermal_warmreset", false)) {
                LOG(INFO) << "Try to trigger a warm reset for thermal shutdown";
                static constexpr const char kThermalShutdownTarget[] = "shutdown,thermal";
                syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                        LINUX_REBOOT_CMD_RESTART2, kThermalShutdownTarget);
            } else {
                reboot(RB_POWER_OFF);
            }
            break;
    }
    // In normal case, reboot should not return.
    PLOG(ERROR) << "reboot call returned";
    abort();
}

3.1.2 创建以及挂载文件系统

第二步,主要是创建和挂载文件系统

//设置允许当前进程创建文件或者目录最大可操作的权限
    umask(0);

//清楚环境变量
    CHECKCALL(clearenv());
//设置环境变量
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
//设置/dev为tmpfs类型,并挂载,且权限为0755,本进程可读可写可执行,本组和其他组只能读写
    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));
//设置/dev/pts为devpts类型,并挂载
    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
// 读取操作系统的启动参数
    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));
//创建kernel log节点
    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)));

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

    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    // /mnt/vendor is used to mount vendor-specific partitions that can not be
    // part of the vendor partition, e.g. because they are mounted read-write.
    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));

    // /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"));

    // /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

查看机器的mount挂载详情表:   adb shell mount

可以看到有很多种文件类型。

$:/ # mount
tmpfs on /dev type tmpfs (rw,seclabel,nosuid,relatime,size=7984680k,nr_inodes=1996170,mode=755)
devpts on /dev/pts type devpts (rw,seclabel,relatime,mode=600,ptmxmode=000)
proc on /proc type proc (rw,relatime,gid=3009,hidepid=invisible)
sysfs on /sys type sysfs (rw,seclabel,relatime)
selinuxfs on /sys/fs/selinux type selinuxfs (rw,relatime)
tmpfs on /mnt type tmpfs (rw,seclabel,nosuid,nodev,noexec,relatime,size=7984680k,nr_inodes=1996170,mode=755,gid=1000)
/dev/block/sda9 on /metadata type ext4 (rw,seclabel,nosuid,nodev,noatime,discard)
/dev/block/dm-2 on / type ext4 (ro,seclabel,nodev,relatime,discard)
/dev/block/dm-3 on /system_ext type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-1 on /product type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-4 on /vendor type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-5 on /vendor_dlkm type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-0 on /odm type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-6 on /mnt/scratch type f2fs (rw,sync,lazytime,seclabel,noatime,background_gc=on,nodiscard,no_heap,user_xattr,inline_xattr,acl,inline_data,inline_dentry,flush_merge,extent_cache,mode=adaptive,active_logs=6,alloc_mode=reuse,checkpoint_merge,fsync_mode=posix,memory=normal)
overlay on /system type overlay (ro,seclabel,noatime,lowerdir=/system,upperdir=/mnt/scratch/overlay/system/upper,workdir=/mnt/scratch/overlay/system/work,override_creds=off)
overlay on /system_ext type overlay (ro,seclabel,noatime,lowerdir=/system_ext,upperdir=/mnt/scratch/overlay/system_ext/upper,workdir=/mnt/scratch/overlay/system_ext/work,override_creds=off)
overlay on /product type overlay (ro,seclabel,noatime,lowerdir=/product,upperdir=/mnt/scratch/overlay/product/upper,workdir=/mnt/scratch/overlay/product/work,override_creds=off)
overlay on /vendor type overlay (ro,seclabel,noatime,lowerdir=/vendor,upperdir=/mnt/scratch/overlay/vendor/upper,workdir=/mnt/scratch/overlay/vendor/work,override_creds=off)
overlay on /vendor_dlkm type overlay (ro,seclabel,noatime,lowerdir=/vendor_dlkm,upperdir=/mnt/scratch/overlay/vendor_dlkm/upper,workdir=/mnt/scratch/overlay/vendor_dlkm/work,override_creds=off)
overlay on /odm type overlay (ro,seclabel,noatime,lowerdir=/odm,upperdir=/mnt/scratch/overlay/odm/upper,workdir=/mnt/scratch/overlay/odm/work,override_creds=off)
...
...

讲一下这些都代表什么意思

tmpfs on /dev type tmpfs (rw,seclabel,nosuid,relatime,size=7984680k,nr_inodes=1996170,mode=755)

tmpfs 挂载点

tmpfs:文件系统类型

/dev:设备文件名

ro: readonly,只读挂载;
rw: read and write, 读写挂载

nosuid 关闭set-user-identifier(设置用户ID)与set-group-identifer(设置组ID)设置位。

mode=755:挂载权限为0755,代表rwxr-xr-x.

权限说明:

权限:0755
含义当前用户组用户group其他用户
可拥有的权限rwxr-xr-x

 一般赋予目录0755权限,文件0644权限。

每个组group的权限范围为0-7,含义如下

ls -l结果二进制含义
---0000no excute , no write ,no read
--x1001excute, (no write, no read)
-w-2010write 
-wx3011write, excute
r--4100read
r-x5101read, excute
rw-  6110read, write
rwx7111read, write , excute

查看设备节点内存,可以看到system,vendor,product都挂载到了哪里。

 adb shell df -h

$:/ # df -h
Filesystem       Size Used Avail Use% Mounted on
tmpfs            7.6G 1.6M  7.6G   1% /dev
tmpfs            7.6G    0  7.6G   0% /mnt
/dev/block/sda9   11M 188K   11M   2% /metadata
/dev/block/dm-2  914M 912M  2.7M 100% /
/dev/block/dm-6  1.9G 230M  1.7G  12% /mnt/scratch
overlay          1.9G 230M  1.7G  12% /system
overlay          1.9G 230M  1.7G  12% /system_ext
overlay          1.9G 230M  1.7G  12% /product
overlay          1.9G 230M  1.7G  12% /vendor
overlay          1.9G 230M  1.7G  12% /vendor_dlkm
overlay          1.9G 230M  1.7G  12% /odm
tmpfs            7.6G 8.0K  7.6G   1% /apex
tmpfs            7.6G 576K  7.6G   1% /linkerconfig
/dev/block/sda2   27M 1.1M   26M   5% /mnt/vendor/persist
/dev/block/sde6  300M  54M  246M  19% /vendor/firmware_mnt
/dev/block/sde11  59M  39M   20M  67% /vendor/dsp
/dev/block/sde7   64M 2.9M   61M   5% /vendor/bt_firmware
/dev/block/sde26  30M    0   30M   0% /mnt/vendor/qmcs
/dev/block/dm-7   45G 4.7G   40G  11% /data
tmpfs            7.6G    0  7.6G   0% /data_mirror 

3.1.3 kernel log初始化

    SetStdioToDevNull(argv);
//可以输出kernel log了,前面已经创建了/dev/dmsg
    InitKernelLogging(argv);

    if (!errors.empty()) {
        for (const auto& [error_string, error_errno] : errors) {
            LOG(ERROR) << error_string << " " << strerror(error_errno);
        }
        LOG(FATAL) << "Init encountered errors starting first stage, aborting";
    }
//此时log系统已经可以用了
    LOG(INFO) << "init first stage started!";

3.1.4 加载/lib/moudles/下的ko文件, 打开串口log

这一部分的代码主要功能如下:

1)打开根目录/

2)加载/lib/moudles/下的ko文件

01-05 23:45:42.399     1     1 I init    : Loading module /lib/modules/qcom_hwspinlock.ko with args ''
01-05 23:45:42.401     1     1 I init    : Loaded kernel module /lib/modules/qcom_hwspinlock.ko
01-05 23:45:42.401     1     1 I init    : Loading module /lib/modules/smem.ko with args ''
01-05 23:45:42.403     1     1 I init    : Loaded kernel module /lib/modules/smem.ko
01-05 23:45:42.403     1     1 I init    : Loading module /lib/modules/minidump.ko with args ''
01-05 23:45:42.408     1     1 I init    : Loaded kernel module /lib/modules/minidump.ko
01-05 23:45:42.408     1     1 I init    : Loading module /lib/modules/qcom-scm.ko with args ''
01-05 23:45:42.416     1     1 I init    : Loaded kernel module /lib/modules/qcom-scm.ko
01-05 23:45:42.416     1     1 I init    : Loading module /lib/modules/qcom_wdt_core.ko with args ''

3)打开串口log

//打开根目录/
    auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
    if (!old_root_dir) {
        PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
    }

    struct stat old_root_info;
    if (stat("/", &old_root_info) != 0) {
        PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
        old_root_dir.reset();
    }

    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0;

    boot_clock::time_point module_start_time = boot_clock::now();
    int module_count = 0;
//加载/lib/modules下的ko文件
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
                           module_count)) {
        if (want_console != FirstStageConsoleParam::DISABLED) {
            LOG(ERROR) << "Failed to load kernel modules, starting console";
        } else {
            LOG(FATAL) << "Failed to load kernel modules";
        }
    }
    if (module_count > 0) {
        auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
                boot_clock::now() - module_start_time);
        setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
        LOG(INFO) << "Loaded " << module_count << " kernel modules took "
                  << module_elapse_time.count() << " ms";
    }


    bool created_devices = false;
//根据ALLOW_FIRST_STAGE_CONSOLE(want_console)决定是否打开串口log
    if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
        if (!IsRecoveryMode()) {
            created_devices = DoCreateDevices();
            if (!created_devices){
                LOG(ERROR) << "Failed to create device nodes early";
            }
        }
        StartConsole(cmdline);
    }

3.1.5 拷贝ramdisk prop文件,创建new ramdisk, 删除old ramdisk

这一部分的代码主要功能如下:

1)打开/system/etc/ramdisk/build.prop文件,如果可以打开,则创建/second_stage_resources/system/etc/ramdisk/build.prop文件,并将/system/etc/ramdisk/build.prop拷贝到second_stage_resources/system/etc/ramdisk/build.prop下。

2)打开/force_debugable, 如果存在“/force_debugable”,则第二阶段init将使用userdebug sepolicy并加载adb_debug.prop以允许adb root

3)创建/first_stage_ramdisk并挂载,然后将根目录切换到/first_stage_ramdisk

4)挂载 system、vendor 、product等系统分区

5)free old ramdisk

//打开/system/etc/ramdisk/build.prop文件
    if (access(kBootImageRamdiskProp, F_OK) == 0) {
//生成/second_stage_resources/system/etc/ramdisk/build.prop
//constexpr const char kSecondStageRes[] = "/second_stage_resources";
//constexpr const char kBootImageRamdiskProp[] = "/system/etc/ramdisk/build.prop";
//inline std::string GetRamdiskPropForSecondStage() {
//    return std::string(kSecondStageRes) + kBootImageRamdiskProp;
//}
        std::string dest = GetRamdiskPropForSecondStage();
        std::string dir = android::base::Dirname(dest);
        std::error_code ec;
        if (!fs::create_directories(dir, ec) && !!ec) {
            LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message();
        }
        if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) {
            LOG(FATAL) << "Can't copy " << kBootImageRamdiskProp << " to " << dest << ": "
                       << ec.message();
        }
        LOG(INFO) << "Copied ramdisk prop to " << dest;
    }

    // If "/force_debuggable" is present, the second-stage init will use a userdebug
    // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
   // 如果存在“/force_debugable”,则第二阶段init将使用userdebug sepolicy并加载adb_debug.prop以允许adb root
    // /userdebug_plat_sepolicy.cil属于selinux策略里的规则
    // 如果设备unlocked(解锁了),则会修改selinux规则,放大用户权限
    if (access("/force_debuggable", F_OK) == 0) {
        std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
        if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
            !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
            LOG(ERROR) << "Failed to setup debug ramdisk";
        } else {
            // setenv for second-stage init to read above kDebugRamdisk* files.
//在second init阶段可以用到
            setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
        }
    }

    if (ForceNormalBoot(cmdline, bootconfig)) {
        mkdir("/first_stage_ramdisk", 0755);
//挂载first_stage_ramdisk到first_stage_ramdisk
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
//将根目录切换到/first_stage_ramdisk
        SwitchRoot("/first_stage_ramdisk");
    }
//挂载 system、vendor 、product等系统分区
    if (!DoFirstStageMount(!created_devices)) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }

    struct stat new_root_info;
//new_root_info是/first_stage_ramdisk
    if (stat("/", &new_root_info) != 0) {
        PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
        old_root_dir.reset();
    }
//old_root_info是/
    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);

3.1.6 重新执行init进程并携带参数selinux_setup

    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
//重新执行/system/bin/init,并携带了参数selinux_setup
    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";

    return 1;

后文接着讲后续流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows安装Docker(无网)

Windows安装Docker&#xff08;无网&#xff09; window无网安装Docker 1. 开启虚拟化功能 1. 开启window的虚拟化功能 方式一&#xff1a;直接在window的搜索框搜索 “启用或关闭windows功能”&#xff0c;就可以快捷进入【启用或关闭windows功能】页面 方式二&#xff1…

Gui基础使用之项目部署

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.gui图形化界面的使用 1.1 前期准备 新建仓库&#xff0c;具体操作如下&#xff1a; 初始化readme文件&…

如何使用fiddler实现手机抓包,Filters过滤器!

一、Fiddler与其他抓包工具的区别 1、Firebug虽然可以抓包&#xff0c;但是对于分析http请求的详细信息&#xff0c;不够强大。模拟http请求的功能也不够&#xff0c;且firebug常常是需要“无刷新修改”&#xff0c;如果刷新了页面&#xff0c;所有的修改都不会保存&#xff1b…

ChatRule:基于知识图推理的大语言模型逻辑规则挖掘11.10

ChatRule&#xff1a;基于知识图推理的大语言模型逻辑规则挖掘 摘要引言相关工作初始化和问题定义方法实验 摘要 逻辑规则对于揭示关系之间的逻辑联系至关重要&#xff0c;这可以提高推理性能并在知识图谱&#xff08;KG&#xff09;上提供可解释的结果。虽然已经有许多努力&a…

Java类和对象详解

文章目录 面向对象概述类和对象类定义和使用定义使用 对象引用对象的初始化和构造构造方法默认初始化就地初始化 面向对象概述 面向对象是一种现在主流的程序设计方法&#xff0c;现如今的大部分语言都支持面向对象&#xff0c;Java的面向对象是由C的面向对象衍生而来&#xf…

AMD发布大小核 CPU,6核心直接砍成单核了

2022年 Intel 第12代酷睿发布&#xff0c;PE 大小核设计被正式带到了 PC 上。 P-Core 也就是传统的大核有着高性能、高功耗&#xff0c;而 E-Core 小核则是更讲究能效比以更低频率运行。 虽说小蝾也曾有对 Windows 调度方面的怀疑&#xff0c;但多线程性能确实实打实证明了其优…

node插件MongoDB(四)—— 库mongoose 操作文档使用(新增、删除、更新、查看文档)(二)

文章目录 前言&#xff08;1&#xff09;问题&#xff1a;安装的mongoose 库版本不应该过高导致的问题&#xff08;2&#xff09;重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 三、修改文档…

react类式组件的生命周期和useEffect实现函数组件生命周期

概念 生命周期是一个组件丛创建,渲染,更新,卸载的过程,无论是vue还是react都具有这个设计概念,也是开发者必须熟练运用的,特别是业务开发,不同的生命周期做不同的事是很重要的. ....多说两句心得,本人是先接触vue的,无论是vue2还是vue3的生命周期,在理解和学习上都会比react更…

echarts饼图label显示不全原因?

echarts饼图label显示不全原因&#xff1f; 标签数量过多&#xff1a;当饼图的扇形数量较多时&#xff0c;为了保证图形的清晰性&#xff0c;ECharts 可能不会显示所有的标签&#xff0c;而是选择显示部分标签或者不显示标签。标签过长&#xff1a;如果标签的文字过长&#xf…

软件测试常用的测试方法详解

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

基于springboot实现家具商城管理系统项目【项目源码】计算机毕业设计

基于springboot实现家具商城管理系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的…

vue+nodejs商城实战项目【登录 + 购物车 + 支付】

从零开始一个前端项目并将其完成需要经历一系列步骤。以下是一个常见的开发流程&#xff0c;可以帮助规划和管理项目&#xff1a; 需求分析和规划&#xff1a; 确定项目的目标和范围。定义用户需求和功能要求。制定项目计划和时间表。 技术选型&#xff1a; 选择适当的前端技术…

Jira Software Enterprise Crack

Jira Software Enterprise Crack Jira软件是为您的应用程序组中的每一个成员设计、监控和启动优秀软件的。 策略&#xff1a;生成用户故事和问题&#xff0c;策略冲刺&#xff0c;并在应用程序团队中分配任务。 跟踪&#xff1a;在具有绝对可见性的完整背景下&#xff0c;确定团…

【论文阅读笔记】Detecting AI Trojans Using Meta Neural Analysis

个人阅读笔记&#xff0c;如有错误欢迎指出&#xff01; 会议&#xff1a;2021 S&P Detecting AI Trojans Using Meta Neural Analysis | IEEE Conference Publication | IEEE Xplore 问题&#xff1a; 当前防御方法存在一些难以实现的假设&#xff0c;或者要求直…

基本数据类型小题两道

根据公式计算A地区教师任教年薪&#xff0c;统计键盘输入的字符串中数字个数&#xff0c;按字典序输出。 (笔记模板由python脚本于2023年11月10日 18:05:18创建&#xff0c;本篇笔记适合熟悉python列表、元、字符串等基本数据类型的coder翻阅) 【学习的细节是欢悦的历程】 Pyth…

vue同时校验多个表单

0 效果 1 代码 checkForm (formRef) {return new Promise((resolve, reject) > {this.$refs[formRef].validate((valid) > {if (valid) {resolve();} else {setTimeout(() > {this.$refs[formRef].clearValidate();reject(new Error(错误));}, 1500);}});}); }, conf…

【unity3D】Input Field组件(可供用户输入的文本框)

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于游戏开发的学习笔记 &#x1f236;本篇是unity的Input Field组件 Input Field组件 基础属性详细解释 Unity中的InputField组件可以用来创建一个可供用户输入的文本框。 基础属性…

Linux内核密码模块

目录 密码算法介绍 Hash摘要算法 Cipher加解密算法 块密码算法 认证算法 MAC和HMAC AEAD算法 Linux内核密码模块的基本构件 Linux内核密码模块介绍 如何使用Linux密码模块 用户层调用Linux内核密码模块的方法 cryptodev AF_ALG 如何开发一个密码引擎驱动 开发一个…

Pycharm常用快捷键和替换正则表达式

原生快捷键的使用&#xff1a; 1.CtrlF&#xff1a;查找 2.CtrlZ&#xff1a;返回上一步 3.Alt 鼠标左键选择&#xff1a;多行同时编辑&#xff08;上、下、左、右键能够移动光标&#xff09; 按住Ctrl,左键点击&#xff0c;定位光标 编辑过程 URL常用的替换正则表达式&am…

从小白到测试专家:掌握Pytest的实用技巧和优秀实践

pytest是一个功能丰富且易于使用的Python测试框架&#xff0c;它建立在Python标准库的unittest模块之上&#xff0c;提供了更简洁、灵活和可读性强的测试代码编写方式。下面&#xff0c;我将按照步骤引导您学习pytest。 步骤1&#xff1a;安装pytest 首先&#xff0c;您需要安装…