V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
LovesAsuna
V2EX  ›  Android

关于安卓两阶段初始化的疑问

  •  
  •   LovesAsuna · 17 小时 55 分钟前 · 530 次点击

    首先要说明一下题主不是做安卓的,只是目前在用的安卓主力机有去刷 magisk ,最近心血来潮想研读一下它的实现,顺便就折腾上了 linux 和安卓的初始化流程

    最近在研究 magisk 是如何实现 systemless 的,然后就开始研究起了 linux 的启动流程和安卓 10+的两阶段初始化,我先说一下我对这个流程的理解:

    在 linux 内核中通过 initrd 将 ramdisk-recovery.img 解压到了 rootfs ,并执行/init ,此时在用户空间进行两阶段初始化,这个/init 文件是编译自 system/core/init 目录。

    在第一阶段中会判断 force_normal_boot,然后创建了 /first_stage_ramdisk ,进行了一些准备工作,然后通过 SwithRoot 将 /first_stage_ramdisk 切换为根目录。

    后面执行 fsm->DoFirstStageMount(),这里是比较关键的一步,这里会调用 MountPartitions 进而到了 FirstStageMountVBootV2::TrySwitchSystemAsRoot(),这个函数做了下面一些事情:

    // If system is in the fstab then we're not a system-as-root device, and in
    // this case, we mount system first then pivot to it.  From that point on,
    // we are effectively identical to a system-as-root device.
    bool FirstStageMountVBootV2::TrySwitchSystemAsRoot() {
        UseDsuIfPresent();
        // Preloading all AVB keys from the ramdisk before switching root to /system.
        PreloadAvbKeys();
    
        auto system_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
            return entry.mount_point == "/system";
        });
    
        if (system_partition == fstab_.end()) return true;
    
        if (use_snapuserd_) {
            SaveRamdiskPathToSnapuserd();
        }
    
        if (!MountPartition(system_partition, false /* erase_same_mounts */)) {
            PLOG(ERROR) << "Failed to mount /system";
            return false;
        }
        if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
            LOG(ERROR) << "check_at_most_once forbidden on external media";
            return false;
        }
    
        SwitchRoot("/system");
    
        return true;
    }
    
    void SwitchRoot(const std::string& new_root) {
        auto mounts = GetMounts(new_root);
    
        LOG(INFO) << "Switching root to '" << new_root << "'";
    
        for (const auto& mount_path : mounts) {
            auto new_mount_path = new_root + mount_path;
            mkdir(new_mount_path.c_str(), 0755);
            if (mount(mount_path.c_str(), new_mount_path.c_str(), nullptr, MS_MOVE, nullptr) != 0) {
                PLOG(FATAL) << "Unable to move mount at '" << mount_path << "' to "
                            << "'" << new_mount_path << "'";
            }
        }
    
        if (chdir(new_root.c_str()) != 0) {
            PLOG(FATAL) << "Could not chdir to new_root, '" << new_root << "'";
        }
    
        if (mount(new_root.c_str(), "/", nullptr, MS_MOVE, nullptr) != 0) {
            PLOG(FATAL) << "Unable to move root mount to new_root, '" << new_root << "'";
        }
    
        if (chroot(".") != 0) {
            PLOG(FATAL) << "Unable to chroot to new root";
        }
    }
    

    从这里可以看出是挂载了 system 分区到 /system 目录,然后执行了 SwitchRoot ,其逻辑是通过 /proc/mounts 读取挂载点然后通过 move mount 的方式将其他挂载点重新挂载到了 /system/xxx 下,然后再次通过 move mount 将 /system 切换到 /,最后执行 chroot(".")。


    那基于上面的分析我有两点疑问:

    1. SwitchRoot("/system") 之后,为什么还能通过 /system/bin/init 来执行 后续的 selinux_setup ?按理说 /system move mount 到 / 后可以看成是将他的内容剪贴到了 / 下,还执行了 chroot("."),此时应该已经没有了 /system 目录了,那这里是怎么找到 init 文件的?
    2. 安卓的两阶段初始化都是在用户空间完成的,从 linux 内核的启动流程上看实际上都还是在 initramfs 。在安卓中已经不需要执行正常 linux 启动流程中的 init_mount 、init_chroot 、try_to_run_init_process 了吗?
    4 条回复    2025-03-14 18:32:20 +08:00
    codehz
        1
    codehz  
       17 小时 39 分钟前
    有没有可能在遍历 mounts 的同时,/system 也在 mount 的数组里,然后也一起被 move mount 了
    emptyqwer
        2
    emptyqwer  
       14 小时 49 分钟前
    1 、执行 chroot(".") 原来的/system/bin/init 变成了/bin/init ,所以实际上系统是再找/bin/init
    2 、是的安卓自定义了这个流程,不需要执行 init_mount 、init_chroot 、try_to_run_init_process
    LovesAsuna
        3
    LovesAsuna  
    OP
       12 小时 26 分钟前 via Android
    @emptyqwer 第一个问题,/system/bin/init 是代里写死的,系统是怎么找到/bin/init 的?我在 linux 上都模拟不出来这种情况
    LovesAsuna
        4
    LovesAsuna  
    OP
       12 小时 25 分钟前 via Android
    @codehz 不会,在 getmounts 里有排除
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1037 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 22:58 · PVG 06:58 · LAX 15:58 · JFK 18:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.