当前位置: 首页 > news >正文

Android Native 之 文件系统挂载

一、文件系统挂载流程概述

二、文件系统挂载流程细节

1、Init启动阶段

众所周知,init进程为android系统的第一个进程,也是native世界的开端,要想让整个android世界能够稳定的运行,文件系统的创建和初始化是必不可少的,这个过程需要在android世界的前面。

//aosp/system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
    LOG(INFO) << "init first stage started!";
    //.....
    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 (!DoFirstStageMount(!created_devices)) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
    //.....
}
//aosp/system/core/init/first_stage_mount.cpp
// Public functions公共函数
// Creates devices and logical partitions from storage devices
bool DoCreateDevices() {
    auto fsm = FirstStageMount::Create();
    if (!fsm.ok()) {
        LOG(ERROR) << "Failed to create FirstStageMount: " << fsm.error();
        return false;
    }
    //来创建设备/即初始化磁盘逻辑分区
    return (*fsm)->DoCreateDevices();
}
// Mounts partitions specified by fstab in device tree.
bool DoFirstStageMount(bool create_devices) {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) {
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }
    auto fsm = FirstStageMount::Create();
    if (!fsm.ok()) {
        LOG(ERROR) << "Failed to create FirstStageMount " << fsm.error();
        return false;
    }
    if (create_devices) {
        if (!(*fsm)->DoCreateDevices()) return false;
    }
    //来进行文件系统的挂载
    return (*fsm)->DoFirstStageMount();
}
//void SetInitAvbVersionInRecovery() 第三个public函数,看起来是和recovery有关系的

Init进程的通过FirstStageMount::Create()来拿到一个fsm对象,然后依次调用fsm的DoCreateDevices和DoFirstStageMount来初始化挂载文件系统。

1.1 FirstStageMount::Create读取fstab配置表

此步骤主要是读取fstab分区配置表,具体实现逻辑其实移交给了fs_mgr

//aosp/system/core/init/first_stage_mount.cpp
using android::fs_mgr::ReadDefaultFstab;
using android::fs_mgr::ReadFstabFromDt;
Result<std::unique_ptr<FirstStageMount>> FirstStageMount::Create() {
    //读取fstab配置表,此表配置了各个目录支持的文件系统相关配置
    auto fstab = ReadFirstStageFstab();
    if (!fstab.ok()) {
        return fstab.error();
    }
    return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
}
static Result<Fstab> ReadFirstStageFstab() {
    Fstab fstab;
    //从DT里面获取,DT好像跟内核有关系,没有具体研究
    if (!ReadFstabFromDt(&fstab)) {
        //读取默认的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());
        } else {
            return Error() << "failed to read default fstab for first stage mount";
        }
    }
    return fstab;
}

fs_mgr被编译成为静态库lib_fs_mgr,这部分逻辑其实就是读取fstab.ini配置文件并进行解析:

//aosp/system/core/fs_mgr/fs_mgr_fstab.cpp
// Loads the fstab file and combines with fstab entries passed in from device tree.
bool ReadDefaultFstab(Fstab* fstab) {
    fstab->clear();
    ReadFstabFromDt(fstab, false /* verbose */);
    std::string default_fstab_path;
    // Use different fstab paths for normal boot and recovery boot, respectively
    if ((access("/sbin/recovery", F_OK) == 0) || (access("/system/bin/recovery", F_OK) == 0)) {
        //recovery模式下读取/etc/recovery.fstab
        default_fstab_path = "/etc/recovery.fstab";
    } else { //正常模式下读取类似于/odm/etc/fstab.
        default_fstab_path = GetFstabPath();
    }
    Fstab default_fstab;
    if (!default_fstab_path.empty() && ReadFstabFromFile(default_fstab_path, &default_fstab)) {
        for (auto&& entry : default_fstab) {
            fstab->emplace_back(std::move(entry));
        }
    } else {
        LINFO << __FUNCTION__ << "(): failed to find device default fstab";
    }

    return !fstab->empty();
}
// Return the path to the fstab file.  There may be multiple fstab files; the
// one that is returned will be the first that exists of fstab.<fstab_suffix>,
// fstab.<hardware>, and fstab.<hardware.platform>.  The fstab is searched for
// in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in
// the first stage ramdisk during early boot.  Previously, the first stage
// ramdisk's copy of the fstab had to be located in the root directory, but now
// the system/etc directory is supported too and is the preferred location.
std::string GetFstabPath() {
    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
        std::string suffix;

        if (!fs_mgr_get_boot_config(prop, &suffix)) continue;

        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."}) {
            std::string fstab_path = prefix + suffix;
            if (access(fstab_path.c_str(), F_OK) == 0) {
                return fstab_path;
            }
        }
    }
    return "";
}

如下Android 14的手机的开机日志,在init阶段来读取fstab配置表的打印:这里的dt没有配置fstab,默认路径没有任何打印,但是可以了解到libfs_mgr的入口

1.2 fstab文件是什么样子的?

 android系统的文件系统相关参数定义被统一放在fstab.in里面,从上面的流程可以了解到fs_mgr会去读取fstab.*文件,并根据此文件配置的内容去逐一挂载所有的分区,那么它到底长什么样子的呢?

首先cat /vendor/etc/fstab.mtxxxx内容如下:

D50:/vendor/etc # cat fsta
fstab.enableswap  fstab.mt6765      fstab.mt8768
D50:/vendor/etc # cat fstab.mt6765
# 1 "vendor/mediatek/proprietary/hardware/fstab/mt6765/fstab.in.mt6765"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 341 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "vendor/mediatek/proprietary/hardware/fstab/mt6765/fstab.in.mt6765" 2
# 145 "vendor/mediatek/proprietary/hardware/fstab/mt6765/fstab.in.mt6765"
system /system ext4 ro wait,avb=vbmeta_system,logical,first_stage_mount,avb_keys=/avb/q-gsi.avbpubkey:/avb/r-gsi.avbpubkey:/avb/s-gsi.avbpubkey,slotselect
system_ext /system_ext ext4 ro wait,avb=vbmeta_system,logical,first_stage_mount,avb_keys=/avb/q-gsi.avbpubkey:/avb/r-gsi.avbpubkey:/avb/s-gsi.avbpubkey,slotselect

vendor /vendor ext4 ro wait,avb,logical,first_stage_mount,slotselect


product /product ext4 ro wait,avb,logical,first_stage_mount,slotselect
# 170 "vendor/mediatek/proprietary/hardware/fstab/mt6765/fstab.in.mt6765"
/dev/block/by-name/md_udc /metadata ext4 noatime,nosuid,nodev,discard wait,check,formattable,first_stage_mount

/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,noflush_merge,reserve_root=134217,resgid=1065,inlinecrypt latemount,wait,check,quota,reservedsize=128M,formattable,resize,,checkpoint=fs,fileencryption=aes-256-xts:aes-256-cts:v2,keydirectory=/metadata/vold/metadata_encryption

/dev/block/by-name/protect1 /mnt/vendor/protect_f ext4 noatime,nosuid,nodev,noauto_da_alloc,commit=1,nodelalloc wait,check,formattable
/dev/block/by-name/protect2 /mnt/vendor/protect_s ext4 noatime,nosuid,nodev,noauto_da_alloc,commit=1,nodelalloc wait,check,formattable
/dev/block/by-name/nvdata /mnt/vendor/nvdata ext4 noatime,nosuid,nodev,noauto_da_alloc,commit=1,nodelalloc wait,check,formattable
/dev/block/by-name/nvcfg /mnt/vendor/nvcfg ext4 noatime,nosuid,nodev,noauto_da_alloc,commit=1,nodelalloc wait,check,formattable

/dev/block/by-name/persist /mnt/vendor/persist ext4 noatime,nosuid,nodev,noauto_da_alloc,commit=1,nodelalloc wait,check,formattable

/devices/platform/externdevice* auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata

/devices/platform/mt_usb* auto vfat defaults voldmanaged=usbotg:auto

/dev/block/by-name/frp /persistent emmc defaults defaults

/dev/block/by-name/nvram /nvram emmc defaults defaults
/dev/block/by-name/proinfo /proinfo emmc defaults defaults
/dev/block/by-name/lk /bootloader emmc defaults defaults
/dev/block/by-name/lk2 /bootloader2 emmc defaults defaults
/dev/block/by-name/para /misc emmc defaults defaults

/dev/block/by-name/boot /boot emmc defaults first_stage_mount,nofail,slotselect
# 210 "vendor/mediatek/proprietary/hardware/fstab/mt6765/fstab.in.mt6765"
/dev/block/by-name/vbmeta_vendor /vbmeta_vendor emmc defaults first_stage_mount,nofail,slotselect
/dev/block/by-name/vbmeta_system /vbmeta_system emmc defaults first_stage_mount,nofail,slotselect,avb=vbmeta

/dev/block/by-name/logo /logo emmc defaults defaults
/dev/block/by-name/expdb /expdb emmc defaults defaults
/dev/block/by-name/seccfg /seccfg emmc defaults defaults

/dev/block/by-name/tee1 /tee1 emmc defaults defaults
/dev/block/by-name/tee2 /tee2 emmc defaults defaults

/dev/block/by-name/scp1 /scp1 emmc defaults defaults
/dev/block/by-name/scp2 /scp2 emmc defaults defaults

/dev/block/by-name/sspm_1 /sspm_1 emmc defaults defaults
/dev/block/by-name/sspm_2 /sspm_2 emmc defaults defaults

/dev/block/by-name/md1img /md1img emmc defaults defaults
/dev/block/by-name/md1dsp /md1dsp emmc defaults defaults
/dev/block/by-name/md1arm7 /md1arm7 emmc defaults defaults
/dev/block/by-name/md3img /md3img emmc defaults defaults

/dev/block/by-name/gz1 /gz1 emmc defaults defaults
/dev/block/by-name/gz2 /gz2 emmc defaults defaults

/dev/block/by-name/spmfw /spmfw emmc defaults defaults

/dev/block/by-name/boot_para /boot_para emmc defaults defaults
/dev/block/by-name/odmdtbo /odmdtbo emmc defaults defaults
/dev/block/by-name/dtbo /dtbo emmc defaults defaults

/dev/block/by-name/vbmeta /vbmeta emmc defaults defaults
D50:/vendor/etc #

如上格式,此文件可以解析如下三部分

那么我们在源代码是如何配置的呢?MTK可以参考如下逻辑,后文详细解读各大配置参数

1.3 FirstStageMount::DoCreateDevices

//aosp/system/core/init/first_stage_mount.cpp
bool FirstStageMount::DoCreateDevices() {
    if (!InitDevices()) return false;
    // Mount /metadata before creating logical partitions, since we need to
    // know whether a snapshot merge is in progress.
    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
        //从fstab配置表中寻找/metadata分区的信息,此分区很重要存储了一些元数据和秘钥相关的东西
        return entry.mount_point == "/metadata";
    });
    if (metadata_partition != fstab_.end()) {
        //首先需要挂载/metadata分区,因为它太重要了
        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
            // Copies DSU AVB keys from the ramdisk to /metadata.
            // Must be done before the following TrySwitchSystemAsRoot().
            // Otherwise, ramdisk will be inaccessible after switching root.
            //它为什么重要,就是因为拷贝AVB Key到这个目录,详细的待后续研究
            CopyDsuAvbKeys();
        }
    }
    //创建逻辑分区
    if (!CreateLogicalPartitions()) return false;
    return true;
}

流程1:如上逻辑首先挂载了/metadata分区,为什么要先挂载它?

流程2:/metadata分区挂载流程对应日志:注意这里调用了metadata_partition函数传递了参数,所以只挂载了一个分区

流程3:创建逻辑分区,那么何为逻辑分区?从下面日志来看个人理解它类似与PC的C盘来区别于其他磁盘,因此这里的逻辑分区通常为system/vendor几个目录

如上日志对应逻辑代码如下:

1.4 FirstStageMount::DoFirstStageMount

//aosp/system/core/init/first_stage_mount.cpp
bool FirstStageMount::DoFirstStageMount() {
    if (!IsDmLinearEnabled() && fstab_.empty()) {
        // Nothing to mount.
        LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
        return true;
    }
    //挂载分区
    if (!MountPartitions()) return false;    
    return true;
}

这里的主要流程还是去调用MountPartitions()去挂载分区,注意这里不像metadata哪里传递了参数,因此这里是根据fstab表去挂载所有其他分区,代码如下:

2、fstab文件参数解读

3、fs_mgr挂载分区

接着init的FirstStageMount::MountPartition通过fstab表来挂载单个分区,如下逻辑,在对底层设备块相关初始化之后通过fs_mgr来进行单个分区的挂载。

//aosp/system/core/init/first_stage_mount.cpp
bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts, Fstab::iterator* end) {
    // Sets end to begin + 1, so we can just return on failure below.
    if (end) {
        *end = begin + 1;
    }
    if (!fs_mgr_create_canonical_mount_point(begin->mount_point)) {
        return false;
    }
    //跟底层设备块有关系,暂时没有深入研究
    if (begin->fs_mgr_flags.logical) {
        if (!fs_mgr_update_logical_partition(&(*begin))) {
            return false;
        }
        if (!block_dev_init_.InitDmDevice(begin->blk_device)) {
            return false;
        }
    }
    if (!SetUpDmVerity(&(*begin))) {
        PLOG(ERROR) << "Failed to setup verity for '" << begin->mount_point << "'";
        return false;
    }
    //核心代码,通过fs_mgr去进行挂载
    bool mounted = (fs_mgr_do_mount_one(*begin) == 0);
    // Try other mounts with the same mount point.
    Fstab::iterator current = begin + 1;
    for (; current != fstab_.end() && current->mount_point == begin->mount_point; current++) {
        if (!mounted) {
            // blk_device is already updated to /dev/dm-<N> by SetUpDmVerity() above.
            // Copy it from the begin iterator.
            current->blk_device = begin->blk_device;
            mounted = (fs_mgr_do_mount_one(*current) == 0);
        }
    }
    if (erase_same_mounts) {
        current = fstab_.erase(begin, current);
    }
    if (end) {
        *end = current;
    }
    return mounted;
}
//aosp/system/core/fs_mgr/fs_mgr.cpp
// wrapper to __mount() and expects a fully prepared fstab_rec,
// unlike fs_mgr_do_mount which does more things with avb / verity etc.
int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) {
    // First check the filesystem if requested.
    if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) {
        LERROR << "Skipping mounting '" << entry.blk_device << "'";
    }
    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
    //步骤1:挂载前的准备工作,其实就是解析fstab分区配置的各种参数
    int ret = prepare_fs_for_mount(entry.blk_device, entry, mount_point);
    // Wiped case doesn't require to try __mount below.
    if (ret & FS_STAT_INVALID_MAGIC) {
      return FS_MGR_DOMNT_FAILED;
    }
    //步骤2:正式进行文件分区的挂载
    ret = __mount(entry.blk_device, mount_point, entry);
    if (ret) {
      ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
    }

    return ret;
}

如上代码进行总结如下:

  • init最后通过libfs_mgr最后调用了fs_mgr.cpp来进行文件分区的挂载
  • 首先通过prepare_fs_for_mount来解析fstab里面配置的一系列参数
  • 最后通过__mount来进行文件分区的挂载

3.1 挂载前的准备工作prepare_fs_for_mount

总结如上代码逻辑,主要做了如下几个步骤:

  • tune_quota:Enable/disable quota support on the filesystem if needed
  • resize_fs:重置文件系统
  • check_fs:校验文件系统
  • tune_reserved_size:ext4支持
  • tune_encrypt:ext4支持
  • tune_verity:ext4支持
  • tune_casefold:ext4支持
  • tune_metadata_csum:ext4支持

3.2 挂载文件分区流程__mount

相关文章:

  • GitCode 助力 vue3-element-admin:开启中后台管理前端开发新征程
  • pytorch图像增强-旋转和平移
  • SAP服务器进程预警通知
  • 使用免费IP数据库离线查询IP归属地
  • C语言中0UL和1UL
  • 记一次联想ThinkBook 16P G5 IRX 系统还原后,进入系统,提示系统还原失败,并且桌面黑屏的只有鼠标指针可以动的问题的解决
  • Vue23Web 基礎性拉滿的面試題(2025版)還沒更新完...
  • 自学嵌入式第29天-----epoll、sqlite3
  • 数据库原理6
  • Flink深入浅出之04:时间、水印、TableSQL
  • 什么是 kafka
  • L1-011 A-B(PTA天梯赛)
  • 【spring】原生xml配置版
  • Java核心语法:从变量到控制流
  • 基于大模型的视神经脊髓炎手术全流程风险预测与诊疗方案研究
  • Android双亲委派
  • 模板注入(Twig)
  • 文件上传漏洞
  • 在CentOS系统上安装Conda的详细指南
  • Kubernetes教程(六)Harbor的安装和使用
  • 营销型网站(易网拓)/日本粉色iphone
  • 网站导航条/上海网站建设费用
  • 做关于植物的网站/企业推广app
  • 建设厅网站如何查询企业信息网/微信营销推广
  • 网站首页流程图/优化关键词排名提升
  • 视频在线制作免费生成/宁德seo