Jimmy Chen

A Programmer

(原创)从代码分析Android分区挂载过程

  本来想直接分析Android磁盘加密部分的代码的。但是看了下代码,还是先认真看看磁盘挂载部分的代码吧,本文以Google Pixel + 8.1.0作为实例分析。

分区挂载入口:init

  init进程是Android的第一个进程,磁盘挂载就是通过它来实现的。init进程主要是通过分析各种.rc脚本来实现对应的动作。Pixel挂载过程使用的.rc文件在device/google/marlin/init.common.rc文件

on fs
    mount_all /vendor/etc/fstab.${ro.hardware} --early

on late-fs
    # Mount RW partitions which need run fsck
    mount_all /vendor/etc/fstab.${ro.hardware} --late

可以看到init初始化设备过程中,共调用了两次mount_all过程,使用的都是同一个fstab文件。对于Pixel,对应的文件路径就是/vendor/etc/fstab.sailfish,文件的内容如下,在分析的时候我们使用userdata这一栏作为例子来进行分析:

#<src>                                                  <mnt_point>         <type>  <mnt_flags and options>     <fs_mgr_flags>
/dev/block/platform/soc/624000.ufshc/by-name/system     /                   ext4    ro,barrier=1                wait,slotselect,verify
/dev/block/platform/soc/624000.ufshc/by-name/modem      /firmware/radio     vfat    ro,shortname=lower,uid=1000,gid=0,dmask=227,fmask=337,context=u:object_r:firmware_file:s0   wait,slotselect
/dev/block/platform/soc/624000.ufshc/by-name/userdata   /data               ext4    errors=panic,noatime,nosuid,nodev,barrier=1,noauto_da_alloc  latemount,wait,check,formattable,fileencryption=ice,quota
/dev/block/zram0                                        none                swap    defaults                    zramsize=536870912,max_comp_streams=4
/dev/block/platform/soc/624000.ufshc/by-name/misc       /misc               emmc    defaults                    defaults
/devices/*/xhci-hcd.0.auto/usb*                         auto                vfat    defaults                    voldmanaged=usb:auto

  在init.rc文件中使用mount_all动作,最后是会调用到do_mount_all函数的。所以下面我们就直接从这个函数开始分析。

do_mount_all开始分析

  do_mount_all函数定义在system/core/init/builtins.cpp中,源码如下:

/* mount_all <fstab> [ <path> ]* [--<options>]*
 *
 * This function might request a reboot, in which case it will
 * not return.
 */
static int do_mount_all(const std::vector& args) {
    std::size_t na = 0;
    bool import_rc = true;
    bool queue_event = true;
    int mount_mode = MOUNT_MODE_DEFAULT;
    // 挂载文件名
    const char* fstabfile = args[1].c_str();
    std::size_t path_arg_end = args.size();
    const char* prop_post_fix = "default";

    for (na = args.size() - 1; na > 1; --na) {
        // 从fstab.sailfish中可以知道,挂载分为early和late
        if (args[na] == "--early") {
            path_arg_end = na;
            queue_event = false;
            mount_mode = MOUNT_MODE_EARLY;
            prop_post_fix = "early";
        } else if (args[na] == "--late") {
            // userdata分区走这里
            path_arg_end = na;
            import_rc = false;
            mount_mode = MOUNT_MODE_LATE;
            prop_post_fix = "late";
        }
    }

    std::string prop_name = "ro.boottime.init.mount_all."s + prop_post_fix;
    android::base::Timer t;
    // mount_fstab的主要工作在这里完成
    int ret =  mount_fstab(fstabfile, mount_mode);
    property_set(prop_name, std::to_string(t.duration().count()));

    if (import_rc) {
        /* Paths of .rc files are specified at the 2nd argument and beyond */
        import_late(args, 2, path_arg_end);
    }

    if (queue_event) {
        /* queue_fs_event will queue event based on mount_fstab return code
         * and return processed return code*/
        ret = queue_fs_event(ret);
    }

    return ret;
}

do_mount_all –> mount_fstab

// 其中方式fstablefile="/vendor/etc/fstab.sailfish"
// mount_mode=MOUNT_MODE_EARLY或者MOUNT_MODE_LATE
static int mount_fstab(const char* fstabfile, int mount_mode) {
    int ret = -1;

    // 这里调用fork创建一个线程来完成mount工作,以防止在mount过程出错导致init进程奔溃
    pid_t pid = fork();
    if (pid > 0) {
        // 父进程会等待子进程完成mount工作,并记录返回值
        int status;
        int wp_ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
        if (wp_ret == -1) {
            // Unexpected error code. We will continue anyway.
            PLOG(WARNING) << "waitpid failed";
        }

        if (WIFEXITED(status)) {
            ret = WEXITSTATUS(status);
        } else {
            ret = -1;
        }
    } else if (pid == 0) {
        android::base::ScopedLogSeverity info(android::base::INFO);

        // 调用fs_mgr_read_fstab,将fstab.sailfish解析填充到fstab结构体
        struct fstab* fstab = fs_mgr_read_fstab(fstabfile);
        // 调用fs_mgr_mount_all执行mount操作
        int child_ret = fs_mgr_mount_all(fstab, mount_mode);
        fs_mgr_free_fstab(fstab);
        if (child_ret == -1) {
            PLOG(ERROR) << "fs_mgr_mount_all returned an error";
        }
        _exit(child_ret);
    } else {
        /* fork failed, return an error */
        return -1;
    }
    return ret;
}

  过程在上面的注释中已经解析得很清楚了,首先会读取并解析fstab.sailfish文件的内容并填充到fstab结构体中,然后在调用fs_mgr_mount_all进行实际mount

其中对fstab.sailfish文件解析得过程没什么好解释的,主要也就是逐行读取文件内容,然后解析填充结构体就可以了。需要注意的是fstab结构体中的flags字段对应关系,后面有可能会用到,这里我将他们截取出来列在下面

static struct flag_list mount_flags[] = {
    { "noatime",    MS_NOATIME },
    { "noexec",     MS_NOEXEC },
    { "nosuid",     MS_NOSUID },
    { "nodev",      MS_NODEV },
    { "nodiratime", MS_NODIRATIME },
    { "ro",         MS_RDONLY },
    { "rw",         0 },
    { "remount",    MS_REMOUNT },
    { "bind",       MS_BIND },
    { "rec",        MS_REC },
    { "unbindable", MS_UNBINDABLE },
    { "private",    MS_PRIVATE },
    { "slave",      MS_SLAVE },
    { "shared",     MS_SHARED },
    { "defaults",   0 },
    { 0,            0 },
};

static struct flag_list fs_mgr_flags[] = {
    {"wait", MF_WAIT},
    {"check", MF_CHECK},
    {"encryptable=", MF_CRYPT},
    {"forceencrypt=", MF_FORCECRYPT},
    {"fileencryption=", MF_FILEENCRYPTION},
    {"forcefdeorfbe=", MF_FORCEFDEORFBE},
    {"keydirectory=", MF_KEYDIRECTORY},
    {"nonremovable", MF_NONREMOVABLE},
    {"voldmanaged=", MF_VOLDMANAGED},
    {"length=", MF_LENGTH},
    {"recoveryonly", MF_RECOVERYONLY},
    {"swapprio=", MF_SWAPPRIO},
    {"zramsize=", MF_ZRAMSIZE},
    {"max_comp_streams=", MF_MAX_COMP_STREAMS},
    {"verifyatboot", MF_VERIFYATBOOT},
    {"verify", MF_VERIFY},
    {"avb", MF_AVB},
    {"noemulatedsd", MF_NOEMULATEDSD},
    {"notrim", MF_NOTRIM},
    {"formattable", MF_FORMATTABLE},
    {"slotselect", MF_SLOTSELECT},
    {"nofail", MF_NOFAIL},
    {"latemount", MF_LATEMOUNT},
    {"reservedsize=", MF_RESERVEDSIZE},
    {"quota", MF_QUOTA},
    {"eraseblk=", MF_ERASEBLKSIZE},
    {"logicalblk=", MF_LOGICALBLKSIZE},
    {"defaults", 0},
    {0, 0},
};

接下来继续看另一个函数

mount_fstab --> fs_mgr_mount_all

// fstab结构体包含fstab.sailfish文件内容
// mount_mode=MOUNT_MODE_EARLY或者MOUNT_MODE_LATE
int fs_mgr_mount_all(struct fstab *fstab, int mount_mode)
{
    int i = 0;
    int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
    int error_count = 0;
    int mret = -1;
    int mount_errno = 0;
    int attempted_idx = -1;
    FsManagerAvbUniquePtr avb_handle(nullptr);

    // fstab解析出错,返回FAIL
    if (!fstab) {
        return FS_MGR_MNTALL_FAIL;
    }

    // 根据fstab记录的条目数量,逐个遍历执行
    for (i = 0; i < fstab->num_entries; i++) {
        // 如果mount flags中带有"voldmanaged="或者"recoveryonly"就跳过
        // 这里对比fstab.sailfish可以知道mount usb的时候是跳过的
        if ((fstab->recs[i].fs_mgr_flags & (MF_VOLDMANAGED | MF_RECOVERYONLY)) ||
             // 如果mount_mode是MOUNT_MODE_LATE,但是flags中没有带latemount也跳过
             ((mount_mode == MOUNT_MODE_LATE) && !fs_mgr_is_latemount(&fstab->recs[i])) ||
             // 如果mount_mode是MOUNT_MODE_EARLY,但是flags中带有latemount就跳过
             ((mount_mode == MOUNT_MODE_EARLY) && fs_mgr_is_latemount(&fstab->recs[i]))) {
            continue;
        }

        /* Skip swap and raw partition entries such as boot, recovery, etc */
        if (!strcmp(fstab->recs[i].fs_type, "swap") ||
            !strcmp(fstab->recs[i].fs_type, "emmc") ||
            !strcmp(fstab->recs[i].fs_type, "mtd")) {
            continue;
        }

        /* Skip mounting the root partition, as it will already have been mounted */
        if (!strcmp(fstab->recs[i].mount_point, "/")) {
            if ((fstab->recs[i].fs_mgr_flags & MS_RDONLY) != 0) {
                fs_mgr_set_blk_ro(fstab->recs[i].blk_device);
            }
            continue;
        }

        /* Translate LABEL= file system labels into block devices */
        if (is_extfs(fstab->recs[i].fs_type)) {
            int tret = translate_ext_labels(&fstab->recs[i]);
            if (tret < 0) {
                LERROR << "Could not translate label to block device";
                continue;
            }
        }

        // flags带有wait,最多等待20秒挂载完成
        if (fstab->recs[i].fs_mgr_flags & MF_WAIT &&
            !fs_mgr_wait_for_file(fstab->recs[i].blk_device, 20s)) {
            LERROR << "Skipping '" << fstab->recs[i].blk_device << "' during mount_all";
            continue;
        }

        if (fstab->recs[i].fs_mgr_flags & MF_AVB) {
            if (!avb_handle) {
                avb_handle = FsManagerAvbHandle::Open(*fstab);
                if (!avb_handle) {
                    LERROR << "Failed to open FsManagerAvbHandle";
                    return FS_MGR_MNTALL_FAIL;
                }
            }
            if (avb_handle->SetUpAvbHashtree(&fstab->recs[i], true /* wait_for_verity_dev */) ==
                SetUpAvbHashtreeResult::kFail) {
                LERROR << "Failed to set up AVB on partition: "
                       << fstab->recs[i].mount_point << ", skipping!";
                /* Skips mounting the device. */
                continue;
            }
        // 是否启用verity,emmmm,这篇重点不在这,后面再开一篇分析
        } else if ((fstab->recs[i].fs_mgr_flags & MF_VERIFY) && is_device_secure()) {
            int rc = fs_mgr_setup_verity(&fstab->recs[i], true);
            if (__android_log_is_debuggable() &&
                    (rc == FS_MGR_SETUP_VERITY_DISABLED ||
                     rc == FS_MGR_SETUP_VERITY_SKIPPED)) {
                LINFO << "Verity disabled";
            } else if (rc != FS_MGR_SETUP_VERITY_SUCCESS) {
                LERROR << "Could not set up verified partition, skipping!";
                continue;
            }
        }

        int last_idx_inspected;
        int top_idx = i;

        // 从这里开始执行真正的mount
        mret = mount_with_alternatives(fstab, i, &last_idx_inspected, &attempted_idx);
        i = last_idx_inspected;
        mount_errno = errno;

        // 这后面的是分区加密的内容,之后再讲。
        if (!mret) {
            int status = handle_encryptable(&fstab->recs[attempted_idx]);

            if (status == FS_MGR_MNTALL_FAIL) {
                /* Fatal error - no point continuing */
                return status;
            }

            if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
                if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
                    // Log and continue
                    LERROR << "Only one encryptable/encrypted partition supported";
                }
                encryptable = status;
            }

            /* Success!  Go get the next one */
            continue;
        }
    .................
}

fs_mgr_mount_all --> mount_with_alternatives

static int mount_with_alternatives(struct fstab *fstab, int start_idx, int *end_idx, int *attempted_idx)
{
    int i;
    int mount_errno = 0;
    int mounted = 0;

    if (!end_idx || !attempted_idx || start_idx >= fstab->num_entries) {
      errno = EINVAL;
      if (end_idx) *end_idx = start_idx;
      if (attempted_idx) *attempted_idx = start_idx;
      return -1;
    }

    /* Hunt down an fstab entry for the same mount point that might succeed */
    for (i = start_idx;
         // 确定mount在合适的mount point上
         i < fstab->num_entries && !strcmp(fstab->recs[start_idx].mount_point, fstab->recs[i].mount_point);
         i++) {
            if (mounted) {
                LERROR << __FUNCTION__ << "(): skipping fstab dup mountpoint="
                       << fstab->recs[i].mount_point << " rec[" << i
                       << "].fs_type=" << fstab->recs[i].fs_type
                       << " already mounted as "
                       << fstab->recs[*attempted_idx].fs_type;
                continue;
            }

            // mount前对参数进行预分析
            int fs_stat = prepare_fs_for_mount(fstab->recs[i].blk_device, &fstab->recs[i]);
            if (fs_stat & FS_STAT_EXT4_INVALID_MAGIC) {
                LERROR << __FUNCTION__ << "(): skipping mount, invalid ext4, mountpoint="
                       << fstab->recs[i].mount_point << " rec[" << i
                       << "].fs_type=" << fstab->recs[i].fs_type;
                mount_errno = EINVAL;  // continue bootup for FDE
                continue;
            }

            int retry_count = 2;
            while (retry_count-- > 0) {
                // __mount最后会调用到底层的mount,进行分区挂载
                if (!__mount(fstab->recs[i].blk_device, fstab->recs[i].mount_point,
                             &fstab->recs[i])) {
                    // 从log来看,userdata分区是挂载会成功的,所以走这里
                    *attempted_idx = i;
                    mounted = 1;
                    if (i != start_idx) {
                        LERROR << __FUNCTION__ << "(): Mounted " << fstab->recs[i].blk_device
                               << " on " << fstab->recs[i].mount_point
                               << " with fs_type=" << fstab->recs[i].fs_type << " instead of "
                               << fstab->recs[start_idx].fs_type;
                    }
                    fs_stat &= ~FS_STAT_FULL_MOUNT_FAILED;
                    mount_errno = 0;
                    break;
                } else {
                    if (retry_count <= 0) break;  // run check_fs only once
                    fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                    /* back up the first errno for crypto decisions */
                    if (mount_errno == 0) {
                        mount_errno = errno;
                    }
                    // retry after fsck
                    check_fs(fstab->recs[i].blk_device, fstab->recs[i].fs_type,
                             fstab->recs[i].mount_point, &fs_stat);
                }
            }
            log_fs_stat(fstab->recs[i].blk_device, fs_stat);
    }

    /* Adjust i for the case where it was still withing the recs[] */
    if (i < fstab->num_entries) --i;

    *end_idx = i;
    if (!mounted) {
        *attempted_idx = start_idx;
        errno = mount_errno;
        return -1;
    }
    return 0;
}

最后我们再看看prepare_fs_for_mount函数

mount_with_alternatives --> prepare_fs_for_mount

static int prepare_fs_for_mount(const char* blk_device, const struct fstab_rec* rec) {
    int fs_stat = 0;

    // 如果是ext类型的分区
    if (is_extfs(rec->fs_type)) {
        // 创建ext4类型的superblock
        struct ext4_super_block sb;

        // 读取ext4分区的superblock,保存到sb中
        if (read_ext4_superblock(blk_device, &sb, &fs_stat)) {
            // 看log输出,大意应该是判断分区是否需要recovery
            if ((sb.s_feature_incompat & EXT4_FEATURE_INCOMPAT_RECOVER) != 0 ||
                (sb.s_state & EXT4_VALID_FS) == 0) {
                LINFO << "Filesystem on " << blk_device << " was not cleanly shutdown; "
                      << "state flags: 0x" << std::hex << sb.s_state << ", "
                      << "incompat feature flags: 0x" << std::hex << sb.s_feature_incompat;
                fs_stat |= FS_STAT_UNCLEAN_SHUTDOWN;
            }

            // Note: quotas should be enabled before running fsck.
            tune_quota(blk_device, rec, &sb, &fs_stat);
        } else {
            return fs_stat;
        }
    }

    // 是否带检查标志,如果有就执行分区检查
    if ((rec->fs_mgr_flags & MF_CHECK) ||
        (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
        check_fs(blk_device, rec->fs_type, rec->mount_point, &fs_stat);
    }

    // 是否带"reservedsize="或者"fileencryption="标志
    if (is_extfs(rec->fs_type) && (rec->fs_mgr_flags & (MF_RESERVEDSIZE | MF_FILEENCRYPTION))) {
        struct ext4_super_block sb;

        if (read_ext4_superblock(blk_device, &sb, &fs_stat)) {
            tune_reserved_size(blk_device, rec, &sb, &fs_stat);
            tune_encrypt(blk_device, rec, &sb, &fs_stat);
        }
    }

    return fs_stat;
}

分区设置的内容大概就到这里了,后面的加密部分,会在下一篇再做分析。

  1. U影动说道:

    你好,
    知道quota的配额限制在哪儿设置的吗?谢谢

    1. jimmychen说道:

      文章最后到prepare_fs_for_mount之后会调用tune_quota函数,应该就是设置quota限额的,如果说是具体配额大小怎么设置的话,这个就没有研究过。希望能帮到你

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: