AndroidT(13) init 进程 -- first stage init 的初始化 (二)

1.概览

第一阶段的 init 工作主要用于读取系统启动阶段需要的配置信息(例如 linux的bootconfig,cmdline等配置信息)、挂载文件系统、安装 kernel 中的模块驱动,最后就是启动第二阶段的 init 来进行 Android 系统相关的组件。第一阶段的 init 被编译为静态的可执行程序,位于 ramdisk 中。在 kernel 启动后该 init 应用程序则会被运行。

system/core/init/first_stage_init.cpp

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

    boot_clock::time_point start_time = boot_clock::now();

    std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \
    if ((x) != 0) errors.emplace_back(#x " failed", errno);

    // Clear the umask.
    umask(0);

    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    // Get the basic filesystem setup we need put together in the initramdisk
    // on / and then we'll let the rc file figure out the rest.
    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));
    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
    // Don't expose the raw commandline to unprivileged processes.
    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));

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

    // This is needed for log wrapper, which gets called before ueventd runs.
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

    // These below mounts are done in first stage init so that first stage mount can mount
    // subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,
    // should be done in rc files.
    // Mount staging areas for devices managed by vold
    // See storage config details at http://source.android.com/devices/storage/
    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

    SetStdioToDevNull(argv);
    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
    // talk to the outside world...
    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(INFO) << "init first stage started!";

    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;
    auto want_parallel =
            bootconfig.find("androidboot.load_modules_parallel = \"true\"") != std::string::npos;

    boot_clock::time_point module_start_time = boot_clock::now();
    int module_count = 0;
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
                           want_parallel, 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;
    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);
    }

    if (access(kBootImageRamdiskProp, F_OK) == 0) {
        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.
    if (access("/force_debuggable", F_OK) == 0) {
        constexpr const char adb_debug_prop_src[] = "/adb_debug.prop";
        constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil";
        std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
        if (access(adb_debug_prop_src, F_OK) == 0 &&
            !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec)) {
            LOG(WARNING) << "Can't copy " << adb_debug_prop_src << " to " << kDebugRamdiskProp
                         << ": " << ec.message();
        }
        if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 &&
            !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec)) {
            LOG(WARNING) << "Can't copy " << userdebug_plat_sepolicy_cil_src << " to "
                         << kDebugRamdiskSEPolicy << ": " << ec.message();
        }
        // setenv for second-stage init to read above kDebugRamdisk* files.
        setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
    }

    if (ForceNormalBoot(cmdline, bootconfig)) {
        mkdir("/first_stage_ramdisk", 0755);
        PrepareSwitchRoot();
        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
        // target directory to itself here.
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            PLOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
        SwitchRoot("/first_stage_ramdisk");
    }

    if (!DoFirstStageMount(!created_devices)) {
        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();

    setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
           1);

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

}  // namespace init
}  // namespace android
### 主要步骤:

1. **错误检查和清理环境变量**- 清除环境变量,设置默认的路径和umask。
   - 挂载一些基本的文件系统,如`/dev``/proc``/sys`等。
   - 创建一些必要的设备节点,如`/dev/kmsg``/dev/random``/dev/urandom`等。
   - 挂载`/mnt``/mnt/vendor``/mnt/product`等分区,以便在第二阶段的init进程中使用。
   - 挂载`/debug_ramdisk``/second_stage_resources`临时文件系统,用于保存一些文件。

2. **初始化内核日志记录**- 初始化内核日志记录系统,以便将内核消息记录到`/dev/kmsg`中。

3. **加载内核模块**- 根据命令行参数和启动配置,加载必要的内核模块。

4. **创建设备节点**- 尝试创建一些早期需要的设备节点,如果在恢复模式下或强制正常启动,则跳过此步骤。

5. **启动控制台**- 根据命令行参数和启动配置,决定是否启动控制台。

6. **复制ramdisk属性文件**- 如果`/force_debuggable`文件存在,则将`/adb_debug.prop``/userdebug_plat_sepolicy.cil`文件复制到`/second_stage_ramdisk`中,以便在第二阶段的init进程中使用。

7. **设置环境变量**- 设置一些环境变量,以便第二阶段的init进程能够使用。

8. **切换根目录**- 如果强制正常启动,则将根目录切换到`/first_stage_ramdisk`9. **执行第一阶段挂载**- 挂载一些必要的设备和文件系统,以便在第二阶段的init进程中使用。

10. **释放ramdisk**- 如果第一阶段挂载成功,则释放第一阶段ramdisk。

11. **设置init的Avb版本**- 在恢复模式下,设置init的Avb版本。

12. **设置环境变量**- 设置一些环境变量,以便第二阶段的init进程能够使用。

13. **执行第二阶段init进程**- 执行`/system/bin/init`程序,启动第二阶段的init进程。 

2.构建必要的目录及设备

这个步骤用于环境的初始化,大概分为下面几类
    a)环境变量的设置
    b)必要文件及设备的创建
    c)对文件及设备的访问权限配置,这里只有传统的 DAC(Discretionary Access Control) 还没启动DAC(Mandatory Access Control).

3.设置 log 的输出位置

我们知道 Android 有自己的 log 系统,但此时它还是不可用的。所以需要将 log 导入到 kernel log 中去,方便调试。

//system\core\init\first_stage_init.cpp
FirstStageMain
    ...
    SetStdioToDevNull(argv);
    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
    // talk to the outside world...
    InitKernelLogging(argv);

4.kernel 驱动模块加载

4.1 所在位置

走到 kernel 启动 init 进程这一阶段时,内存中的 ramdisk 内容则是由 vendor_boot ramdik 和 init_boot ramdisk 组成的,下面是官网给出的示意图,这部分工作则是在 bootloader 阶段完成的,在此不做赘述

在这里插入图片描述
 kernel 模块是位于 vendor_boot ramdik 的,所以有必要展示下 vendor_boot 的目录结构

├── bootconfig
├── dtb
├── vendor_ramdisk
|__ vendor-ramdisk-by-name

vendor_ramdisk 的展开结构如下

├── acct
├── apex
├── config
├── data
├── data_mirror
├── debug_ramdisk
├── default.prop
├── dev
├── first_stage_ramdisk
│   └── fstab.s5e8835
├── init.recovery.s5e8835.rc
├── lib
│   └── modules
│       ├── first_module.ko
│       ├-- ...
│       ├── modules.alias
│       ├── modules.dep
│       ├── modules.load.recovery
│       ├── modules.softdep
│       └── last_module.ko
├── linkerconfig
├── metadata
├── mnt
├── odm
├── odm_dlkm
├── odm_file_contexts
├── odm_property_contexts
├── oem
├── plat_file_contexts
├── plat_property_contexts
├── plat_service_contexts
├── postinstall
├── proc
├── product_file_contexts
├── product_property_contexts
├── product_service_contexts
├── prop.default
├── res
│   └── images
│       ├── ...
│       └── stage_fill.png
├── sdcard
├── second_stage_resources
├── sepolicy
├── storage
├── sys
├── system
│   ├── bin
│   ├── ├── ...
│   │   └── ziptool
│   ├── etc
│   │   ├── cgroups.json
│   │   ├── init
│   │   │   ├── hw
│   │   │   │   └── init.rc
│   │   │   └── servicemanager.recovery.rc
│   │   ├── ld.config.txt
│   │   ├── mke2fs.conf
│   │   ├── recovery.fstab
│   │   ├── security
│   │   │   └── otacerts.zip
│   │   └── ueventd.rc
│   └── lib64
│       ├── ...
│       ├── android.hardware.health-V1-ndk.so
│       ├── hw
│       │   ├── ...
│       │   └── android.hardware.health@2.0-impl-default.so
│       ├── ...
│       └── libz.so
├── system_dlkm
├── system_ext_file_contexts
├── system_ext_property_contexts
├── system_ext_service_contexts
├── tmp
├── vendor
├── vendor_dlkm
├── vendor_file_contexts
├── vendor_property_contexts
└── vendor_service_contexts

4.2 模块目录统计策略

kernel 中的模块加载则是在这一阶段进行的,下面是对应的代码

//system\core\init\first_stage_init.cpp
FirstStageMain
    //code 1
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console, want_parallel, module_count))
    //code 2
    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";
    }

code 1 中,LoadKernelModules 只支持对目录 /lib/modules 深度为 1 的模块的加载

//system\core\init\first_stage_init.cpp
#define MODULE_BASE_DIR "/lib/modules"
bool LoadKernelModules(bool recovery, bool want_console, bool want_parallel, int& modules_loaded)
    std::unique_ptr<DIR, decltype(&closedir)> base_dir(opendir(MODULE_BASE_DIR), closedir);
    std::vector<std::string> module_dirs;
    while ((entry = readdir(base_dir.get()))) {
        if (entry->d_type != DT_DIR) {
            continue;
        }
        sscanf(entry->d_name, "%d.%d", &dir_major, &dir_minor)
        module_dirs.emplace_back(entry->d_name);
    }

可以看到,它只统计 /lib/modules 目录下的第一级目录,并记录在 module_dirs vector 中,不会递归的查找。

4.3 模块的加载策略

先看下整体的代码逻辑

//system\core\init\first_stage_init.cpp
#define MODULE_BASE_DIR "/lib/modules"
FirstStageMain
    for (const auto& module_dir : module_dirs) {
        //code 1
        std::string dir_path = MODULE_BASE_DIR "/";
        dir_path.append(module_dir);
        //code 2
        Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
        //code 3
        bool retval = m.LoadListedModules(!want_console);
        //code 4
        modules_loaded = m.GetModuleCount();
    }

可见整体的代码逻辑还是相当清晰的,遍历 module_dirs 中所有的目录,对每一个目录调用 Modprobe 工具类来加载。
  下面先看看工具类 Modprobe,它是独立的一个库 libmodprobe

//system\core\libmodprobe\Android.bp
cc_library_static {
    name: "libmodprobe",
    vendor_available: true,
    ramdisk_available: true,
    recovery_available: true,
    srcs: [
        "libmodprobe.cpp",
        "libmodprobe_ext.cpp",
    ],
    shared_libs: [
        "libbase",
    ],
    export_include_dirs: ["include/"],
}

code 2 中 GetModuleLoadList 的定义如下

std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
    auto module_load_file = "modules.load";
    if (recovery) {
        struct stat fileStat;
        std::string recovery_load_path = dir_path + "/modules.load.recovery";
        if (!stat(recovery_load_path.c_str(), &fileStat)) {
            module_load_file = "modules.load.recovery";
        }
    }
    return module_load_file;
}

如果是 recovery 模式那么就使用 modules.load.recovery 反之则是modules.load。所以对于正常启动此处会返回 modules.load。下面来看看它的构造过程

Modprobe m({dir_path}/*base_paths*/, "modules.load"/*load_file*/);
Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file, bool use_blocklist)
    for (const auto& base_path : base_paths) {
        //code 2-1
        auto alias_callback = std::bind(&Modprobe::ParseAliasCallback, this, _1);
        ParseCfg(base_path + "/modules.alias", alias_callback);
        ...
        //code 2-2
        auto dep_callback = std::bind(&Modprobe::ParseDepCallback, this, base_path, _1);
        ParseCfg(base_path + "/modules.dep", dep_callback);
        ...
        //code 2-3
        auto softdep_callback = std::bind(&Modprobe::ParseSoftdepCallback, this, _1);
        ParseCfg(base_path + "/modules.softdep", softdep_callback);
        ...
        //code 2-4
        auto load_callback = std::bind(&Modprobe::ParseLoadCallback, this, _1);
        ParseCfg(base_path + "/" + load_file, load_callback);
        ...
        //code 2-5
        auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
        ParseCfg(base_path + "/modules.options", options_callback);
        ...
        //code 2-6
        auto blocklist_callback = std::bind(&Modprobe::ParseBlocklistCallback, this, _1);
        ParseCfg(base_path + "/modules.blocklist", blocklist_callback);
    }

Modprobe 构造方法中仅仅只是将各个文件的内容进行解析并存入对于的变量中去,供后续使用。下面则列出他们最终被存入的变量
 code 2-1

//\system\core\libmodprobe\libmodprobe.cpp
ParseAliasCallback
    this->module_aliases_.emplace_back(alias, module_name);

code 2-2

ParseDepCallback
    deps.push_back(prefix + *arg);
    this->module_deps_[canonical_name] = deps;

code 2-3

ParseSoftdepCallback
    while (it != args.end()) {
        if (state == "pre:") {
            this->module_pre_softdep_.emplace_back(module, token);
        } else {
            this->module_post_softdep_.emplace_back(module, token);
        }
    }

code 2-4

ParseLoadCallback
    this->module_load_.emplace_back(canonical_name);

code 2-5

ParseOptionsCallback
    auto [unused, inserted] = this->module_options_.emplace(canonical_name, options);

code 2-6

ParseBlocklistCallback
    this->module_blocklist_.emplace(canonical_name);

code 3 中,就会根据上面初始化的值进行 kernel 模块的加载了,其实现如下

//system\core\libmodprobe\libmodprobe.cpp
bool retval = m.LoadListedModules(!want_console);
    auto ret = true;
    for (const auto& module : module_load_) {
        if (!LoadWithAliases(module, true)) {
            if (IsBlocklisted(module)) continue;
            ret = false;
            if (strict) break;
        }
    }
    return ret;

LoadWithAliases 里面涉及的模块则是来自于文件 /lib/modules/modules.alias。

//system\core\libmodprobe\libmodprobe.cpp
Modprobe::LoadWithAliases(const std::string& module_name, ...)
    std::set<std::string> modules_to_load = {canonical_name};
    for (const auto& [alias, aliased_module] : module_aliases_) {
        ...
        modules_to_load.emplace(aliased_module);
    }

    for (const auto& module : modules_to_load) {
        if (InsmodWithDeps(module, parameters))
            module_loaded = true;
    }

将事先准备好的 module_aliases_ 内容过滤下再调用 InsmodWithDeps 进行安装。

//system\core\libmodprobe\libmodprobe.cpp
InsmodWithDeps
    auto dependencies = GetDependencies(module_name);
    for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep)
        LoadWithAliases(*dep, true)
    // load target module itself with args
    Insmod(dependencies[0], parameters)

从上面代码也可以看出来,在安装模块前,则会先安装他们的依赖。最后再安装 当前 kernel 模块。

4.4 加载时间的统计

加载时间在优化系统启动时间时还是很重要的,kernle 模块的加载可以通过搜索 如下 log 查看

kernel modules took

它的实现如下

    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";
    }
setenv append_bootargs printk.devkmsg=on initcall_debug

可以看到,此时间还被记录到环境变量中去了 kEnvInitModuleDurationMs

//system\core\init\first_stage_init.h
static constexpr char kEnvInitModuleDurationMs[] = "INIT_MODULE_DURATION_MS";

5.属性文件的处理

//system\core\init\second_stage_resources.h
constexpr const char kSecondStageRes[] = "/second_stage_resources";
constexpr const char kBootImageRamdiskProp[] = "/system/etc/ramdisk/build.prop";
//system\core\init\first_stage_init.cpp
FirstStageMain
    if (access(kBootImageRamdiskProp, F_OK) == 0) {
        //system\core\init\second_stage_resources.h
        std::string dest = GetRamdiskPropForSecondStage();
            //return /second_stage_resources/system/etc/ramdisk/build.prop
            return std::string(kSecondStageRes) + kBootImageRamdiskProp;
        std::string dir = android::base::Dirname(dest);
        fs::copy_file(kBootImageRamdiskProp, dest, ec)
    }

对于 ramdisk 中的属性文件,最终也会反应到 Android 的属性服务中去的。但是 ramdisk 在 Android 启动后会被卸载掉,所以这里需要做下拷贝操作。
  它的路径如下

/second_stage_resources/system/etc/ramdisk/build.prop

6.是否允许root

//system\core\init\second_stage_resources.h
constexpr const char kDebugRamdiskProp[] = "/debug_ramdisk/adb_debug.prop";
//system\core\init\first_stage_init.cpp
FirstStageMain
    if (access("/force_debuggable", F_OK) == 0) {
        constexpr const char adb_debug_prop_src[] = "/adb_debug.prop";
        constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil";
        if (access(adb_debug_prop_src, F_OK) == 0 &&
            !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec))
        if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 &&
            !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec))
        // setenv for second-stage init to read above kDebugRamdisk* files.
        setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
    }

所以只要 ramdisk 中存在 /force_debuggable 就代表着要开启调试模式,这也意味着adb是可用的,并且是支持 root 的。

7.切换根目录

FirstStageMain
    if (ForceNormalBoot(cmdline, bootconfig)) {
        mkdir("/first_stage_ramdisk", 0755);
        PrepareSwitchRoot();
        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
        // target directory to itself here.
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            PLOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
        SwitchRoot("/first_stage_ramdisk");
    }

代码很简单,提出来是为了提示接下去根目录就变化了。

8.根据配置挂在第一阶段的文件系统

FirstStageMain
    //system\core\init\first_stage_mount.cpp
    DoFirstStageMount(!created_devices)
        //code 1
        auto fsm = FirstStageMount::Create();
        //code 2
        (*fsm)->DoCreateDevices()
        //code 3
        return (*fsm)->DoFirstStageMount();

可以看到流程很清晰,下面来看看他们的具体实现。

8.1 code 1

//system\core\init\first_stage_mount.cpp
auto fsm = FirstStageMount::Create();
    auto fstab = ReadFirstStageFstab()
        //Fstab fstab;
        if (!ReadFstabFromDt(&fstab)) {
            if (ReadDefaultFstab(&fstab)) {
                fstab.erase(std::remove_if(fstab.begin(), fstab.end(),
                                        [](const auto& entry) {
                                            return !entry.fs_mgr_flags.first_stage_mount;
                                        }),
                            fstab.end());
            }
        }
        return fstab;
    return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));

可以看出,挂在表如果在 Deivce tree 中获取到了,那么就不再调用 ReadDefaultFstab,两者是互斥的。我们使用 ReadDefaultFstab 来获取,所以下面来看看他的实现

//system\core\fs_mgr\fs_mgr_fstab.cpp
ReadDefaultFstab(Fstab* fstab)
    ReadFstabFromDt(fstab, false /* verbose */);
    default_fstab_path = GetFstabPath();
    Fstab default_fstab;
    ReadFstabFromFile(default_fstab_path, &default_fstab)
    for (auto&& entry : default_fstab) {
        fstab->emplace_back(std::move(entry));
    }
    return true;

我们依然忽略掉从 Device tree 中获取的部分,看下查找 fs 文件的顺序

//system\core\fs_mgr\fs_mgr_fstab.cpp
GetFstabPath
    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
        std::string suffix;
        //a
        if (!fs_mgr_get_boot_config(prop/*key*/, &suffix/*out_val*/)) continue;
        //b
        for (const char* prefix : {// late-boot/post-boot locations
                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
                                   // early boot locations
                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
            //c
            std::string fstab_path = prefix + suffix;
            if (access(fstab_path.c_str(), F_OK) == 0) {
                return fstab_path;
            }
        }
    }

从上面的代码 c 处可以看到,路径由前缀和后缀构成。
  前缀的实现代码如下

//system\core\fs_mgr\fs_mgr_boot_config.cpp
fs_mgr_get_boot_config(const std::string& key, std::string* out_val)
    // next, check if we have "ro.boot" property already
    *out_val = android::base::GetProperty("ro.boot." + key, "");
    if (!out_val->empty()) {
        return true;
    }
    // next, check if we have the property in bootconfig
    if (fs_mgr_get_boot_config_from_bootconfig_source(key, out_val)) {
        return true;
    }

    // finally, fallback to kernel cmdline, properties may not be ready yet
    if (fs_mgr_get_boot_config_from_kernel_cmdline(key, out_val)) {
        return true;
    }

可以看出,在每一个key中获取后缀的顺序如下并且任何一个获取到后就立即返回,以该值为准,属性值->bootconfig->kernel cmdline。
  一般来说,到 ro.boot.hardware 属性的获取就可以确定了,如下

flg1080:/ # getprop ro.boot.hardware
staf1080

那么此时的后缀就为 staf1080。
  后缀确定后,也就很好确定获取文件系统挂在顺序了
    1) /odm/etc/fstab.staf1080
    2) /vendor/etc/fstab.staf1080
    3) /system/etc/fstab.staf1080
    4) /first_stage_ramdisk/system/etc/fstab.staf1080
    5) /fstab.staf1080
    6) /first_stage_ramdisk/fstab.staf1080
  注意了,上面的路径是按先后顺序只取一例,不做糅合的。
  最后 Create 返回的是 FirstStageMountVBootV2 数据结构,看看它的类图
在这里插入图片描述
再看看它的构造

return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
    //system\core\init\first_stage_mount.cpp
    FirstStageMountVBootV2((Fstab fstab)
        ...
        for (const auto& entry : fstab_) {
            if (!entry.vbmeta_partition.empty()) {
                vbmeta_partitions_.emplace_back(entry.vbmeta_partition);
            }
        }
        ...

构造方法中都是 vbmeta 的内容,这部分属于安全的内容,在此则不做赘述。

8.2 code 2

//system\core\init\first_stage_mount.cpp
(*fsm)->DoCreateDevices()
||
FirstStageMount::DoCreateDevices()
    InitDevices()
        GetSuperDeviceName(&devices);
        GetDmVerityDevices(&devices)
        InitRequiredDevices(std::move(devices))
            //system\core\init\block_dev_initializer.cpp
            return block_dev_init_.InitDevices(std::move(devices));
    CreateLogicalPartitions

其中 InitDevices 做的也很简单,从 fstab 中获取到各个分区的设备信息,然后 polling 等待 uevnet信息,如果是对应的block设备事件则调用它的处理方法进行处理

//system\core\init\devices.cpp
HandleDevice
    if (action == "add" || (action == "change" && StartsWith(devpath, "/dev/block/dm-"))){
        ...
    }
    if (action == "remove") {
        ...
    }

CreateLogicalPartitions最终调用 android::fs_mgr::CreateLogicalPartitions 进行处理

//system\core\fs_mgr\fs_mgr_dm_linear.cpp
CreateLogicalPartitions(const std::string& block_device)
    return CreateLogicalPartitions(*metadata.get(), block_device);
        ...

8.3 code 3

最后开始工具 fs配置文件进行文件系统挂载了

return (*fsm)->DoFirstStageMount();
    //system\core\init\first_stage_mount.cpp
    return MountPartitions()
        TrySwitchSystemAsRoot
        for (auto current = fstab_.begin(); current != fstab_.end();) {
            MountPartition(current, false /* erase_same_mounts */, &end)
        }

        for (const auto& entry : fstab_) {
            if (entry.fs_type == "overlay") {
                fs_mgr_mount_overlayfs_fstab_entry(entry);
            }
        }

        // If we don't see /system or / in the fstab, then we need to create an root entry for
        // overlayfs.
        if (!GetEntryForMountPoint(&fstab_, "/system") && !GetEntryForMountPoint(&fstab_, "/")) {
            FstabEntry root_entry;
            if (GetRootEntry(&root_entry)) {
                fstab_.emplace_back(std::move(root_entry));
            }
        }

        MapScratchPartitionIfNeeded(&fstab_, init_devices);
        fs_mgr_overlayfs_mount_all(&fstab_);

9.启动下一阶段的 init

在完成这一次的使命后,first init 也到了该退出的时候了。

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));

execv的关键点则是会使用 path 指向的可执行程序替换当前进程已在执行的程序,用人话讲就是 first init 被 /system/bin/init 替换了,下面就会执行 /system/bin/init 中的 main 入口方法了。
  所以上面代码等价于在 shell 中执行如下命令,并且它的 pid 保持不变及为 1

/system/bin/init selinux_setup
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值