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

1.概览

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

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

  这个步骤用于环境的初始化,大概分为下面几类
    a)环境变量的设置
    b)必要文件及设备的创建
    c)对文件及设备的访问权限配置,这里只有传统的 DAC(Discretionary Access Control) 还没启动DAC(Mandatory Access Control).对 DAC 和 MAC 有疑问的可以参考之前的博客《Android R(11)HIDL服务的sepolicy(五)》。

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

  可以看到,此时间还被记录到环境变量中去了 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
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值