首先要说明一下题主不是做安卓的,只是目前在用的安卓主力机有去刷 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(".")。
那基于上面的分析我有两点疑问:
- SwitchRoot("/system") 之后,为什么还能通过 /system/bin/init 来执行 后续的 selinux_setup ?按理说 /system move mount 到 / 后可以看成是将他的内容剪贴到了 / 下,还执行了 chroot("."),此时应该已经没有了 /system 目录了,那这里是怎么找到 init 文件的?
- 安卓的两阶段初始化都是在用户空间完成的,从 linux 内核的启动流程上看实际上都还是在 initramfs 。在安卓中已经不需要执行正常 linux 启动流程中的 init_mount 、init_chroot 、try_to_run_init_process 了吗?