android分区挂载fstab

SoC : RK3288
Platform : Android 5.1

本文简述android启动后分区是如何挂载,简述fstab的作用。

一、fstab
Android 5.1分区挂载配置文件:fstab.xxxx.xxxx,不同于android4.4(vold.fstab),在5.1后分区配置文件发生了变化。fstab定义如下:

<src> <mount point>  <filesystem type> <mount flags parameters>     <fs_mgr_flags> 
/dev/.. /mnt/internal_sd  vfat               defaults         voldmanaged=internal_sd:14,nomulated

mount flags parameters文件系统的参数:
async/sync : 设置是否为同步方式运行
auto/noauto : 当下载mount -a 的命令时,此文件系统是否被主动挂载。默认为auto
rw/ro : 是否以以只读或者读写模式挂载
exec/noexec : 限制此文件系统内是否能够进行”执行”的操作
user/nouser : 是否允许用户使用mount命令挂载
suid/nosuid : 是否允许SUID的存在
usrquota : 启动文件系统支持磁盘配额模式
grpquota : 启动文件系统对群组磁盘配额模式的支持
defaults : 同时具有rw,suid,dev,exec,auto,nouser,async等默认参数的设置

fstab有何作用呢?
通过配置fstab,vold服务通过函数process_config调用fs_mgr_read_fstab来完成对分区文件的解析。
fs_mgr_read_fstab—>fs_mgr进程,该服务源码位于system/core/fs_mgr/fs_mgr_main.c,主要是解析分区文件,并完成挂载任务的最终工作者。
接下来,我们来看看fstab是如何被init获取的。

二、init
kernel加载完后第一个执行的就是init进程,init进程会根据init.rc的规则启动进程或者服务,init.rc通过
import /init.${ro.hardware}.rc导入平台的规则。device/rockchip/common/init.rk30board.rc中:

import init.${ro.hardware}.bootmode.{ro.bootmode}.rc    -----> init.rk30board.bootmode.emmc.rc

device/rockchip/common/init.rk30board.bootmode.emmc.rc中:

on fs
        mount_all  fstab.rk30board

mount_all是一条命令,fstab.rk30board是传入的参数,在system/core/init/keywords.h中,定义了mount_all命令:

KEYWORD(mount_all,   COMMAND, 1, do_mount_all)

从以上定义中看出,mount_all命令对应的是do_mount_all函数,fstab.rk30board是do_mount_all函数的参数。do_mount_all()位于system/core/init/builtins.c中:

/*
 * This function might request a reboot, in which case it will
 * not return.
 */
int do_mount_all(int nargs, char **args)
{
    pid_t pid;
    int ret = -1;
    int child_ret = -1;
    int status;
    const char *prop;
    struct fstab *fstab;

    if (nargs != 2) {
        return -1;
    }

    /*
     * Call fs_mgr_mount_all() to mount all filesystems.  We fork(2) and
     * do the call in the child to provide protection to the main init
     * process if anything goes wrong (crash or memory leak), and wait for
     * the child to finish in the parent.
     */
    pid = fork();
    if (pid > 0) {
        /* Parent.  Wait for the child to return */
        int wp_ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
        if (wp_ret < 0) {
            /* Unexpected error code. We will continue anyway. */
            NOTICE("waitpid failed rc=%d, errno=%d\n", wp_ret, errno);
        }

        if (WIFEXITED(status)) {
            ret = WEXITSTATUS(status);
        } else {
            ret = -1;
        }
    } else if (pid == 0) {
        /* child, call fs_mgr_mount_all() */
        klog_set_level(6);  /* So we can see what fs_mgr_mount_all() does */
        fstab = fs_mgr_read_fstab(args[1]);     //解析分区文件fstab
        child_ret = fs_mgr_mount_all(fstab);
        fs_mgr_free_fstab(fstab);
        if (child_ret == -1) {
            ERROR("fs_mgr_mount_all returned an error\n");
        }
        _exit(child_ret);
    } else {
        /* fork failed, return an error */
        return -1;
    }

    if (ret == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
        property_set("vold.decrypt", "trigger_encryption");
    } else if (ret == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
        property_set("ro.crypto.state", "encrypted");
        property_set("vold.decrypt", "trigger_default_encryption");
    } else if (ret == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
        property_set("ro.crypto.state", "unencrypted");
        /* If fs_mgr determined this is an unencrypted device, then trigger
         * that action.
         */
        action_for_each_trigger("nonencrypted", action_add_queue_tail);
    } else if (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
        /* Setup a wipe via recovery, and reboot into recovery */
        ERROR("fs_mgr_mount_all suggested recovery, so wiping data via recovery.\n");
        ret = wipe_data_via_recovery();
        /* If reboot worked, there is no return. */
    } else if (ret > 0) {
        ERROR("fs_mgr_mount_all returned unexpected error %d\n", ret);
    }
    /* else ... < 0: error */

    return ret;
}

上述函数最终调用fs_mgr_read_fstab(args[1])加载分区挂载文件的内容到fstab结构体中,最后通过 fs_mgr_mount_all(fstab)挂载分区。args[1]就是传给do_mount_all的参数fstab.rk30board

这里又产生一个疑问?传入的参数是fstab.rk30board,为什么不是fstab.rk30board.bootmode.emmc呢?
在out/target/product/rk3288_box/root/下没有发现fstab.rk30board文件,但是在系统启动后,adb shell下根目录中可以看到有fstab.rk30board文件,该文件和fstab.rk30board.bootmode.emmc文件的内容相同。因此do_mount_all打开文件fstab.rk30board是OK的。那么,fstab.rk30board文件是何时被创建的呢?在adb shell下输入:

ls -l fstab.rk30board
lrwxrwxrwx root     root     2016-11-28 07:22 fstab.rk30board -> /fstab.rk30board.bootmode.emmc

fstab.rk30board是链接到fstab.rk30board.bootmode.emmc的。搜索源码,最终在init.c中找到,简单分析下init,在main()中,调用process_kernel_cmdline()函数,获取/proc/cmdline

static void process_kernel_cmdline(void)
{
    /* don't expose the raw commandline to nonpriv processes */
    chmod("/proc/cmdline", 0440);

    /* first pass does the common stuff, and finds if we are in qemu.
     * second pass is only necessary for qemu to export all kernel params
     * as props.
     */
    import_kernel_cmdline(0, import_kernel_nv);
    if (qemu[0])
        import_kernel_cmdline(1, import_kernel_nv);

    /* now propogate the info given on command line to internal variables
     * used by init as well as the current required properties
     */
    export_kernel_boot_props();
}

这里的/proc/cmdline参数和parameters中的并不相同,只是在parameters内容的后面加了一部分参数,其中就包括androidboot.mode=emmc,指定了bootmode=emmc,后续讲述。调用export_kernel_boot_props()函数

static void export_kernel_boot_props(void)
{
    char tmp[PROP_VALUE_MAX];
    int ret;
    unsigned i;
    struct {
        const char *src_prop;
        const char *dest_prop;
        const char *def_val;
    } prop_map[] = {
#ifdef TARGET_BOARD_PLATFORM_SOFIA3GR
        { "ro.boot.serialno", "ro.serialno", "", },
#endif
        { "ro.boot.mode", "ro.bootmode", "unknown", },
        { "ro.boot.baseband", "ro.baseband", "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
    };

    for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
        ret = property_get(prop_map[i].src_prop, tmp);
        if (ret > 0)
            property_set(prop_map[i].dest_prop, tmp);
        else
            property_set(prop_map[i].dest_prop, prop_map[i].def_val);
    }

    ret = property_get("ro.boot.console", tmp);
    if (ret)
        strlcpy(console, tmp, sizeof(console));

    /* save a copy for init's usage during boot */
    property_get("ro.bootmode", tmp);
    strlcpy(bootmode, tmp, sizeof(bootmode));

    /* if this was given on kernel command line, override what we read
     * before (e.g. from /proc/cpuinfo), if anything */
    ret = property_get("ro.boot.hardware", tmp);
    if (ret)
        strlcpy(hardware, tmp, sizeof(hardware));
    property_set("ro.hardware", hardware);

    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);

    /* TODO: these are obsolete. We should delete them */
    if (!strcmp(bootmode,"factory"))
        property_set("ro.factorytest", "1");
    else if (!strcmp(bootmode,"factory2"))
        property_set("ro.factorytest", "2");
    else
        property_set("ro.factorytest", "0");
    #ifdef NAND_EMMC
    sysmlink_nand_emmc_fstab();
    #endif
    symlink_fstab();
}

再调用symlink_fstab()函数

static void symlink_fstab()
{
    char fstab_path[255] = "/fstab.";
    char fstab_default_path[50] = "/fstab.";
    int ret = -1;

    // fstab.rk30board.bootmode.unknown
    strcat(fstab_path, hardware);                 //fstab.rk30board
    strcat(fstab_path, ".bootmode.");             //fstab.rk30board.bootmode.
    strcat(fstab_path, bootmode);                 //fstab.rk30board.bootmode.emmc

    strcat(fstab_default_path, hardware);        //fstab.rk30board

    //symlink  /fstab.rk30board.bootmode.emmc  /fstab.rk30board
    ret = symlink(fstab_path, fstab_default_path);  
    if (ret < 0) {
        ERROR("%s : failed", __func__);
    }
}

因此,就是在这里将fstab.rk30board.bootmode.emmc软链接到fstab.rk30board,在后面调用do_mount_all时打开fstab.rk30board解析分区内容。
有关解析分区文件并挂载的fs_mgr源码,有待分析。

三、bootmode
前文讲述到在/proc/cmdline中加入了参数androidboot.mode=emmc,build.prop重没有ro.bootmode,又是如何获取到androidboot.mode并添加的呢?
其实ro.bootmode的属性是通过ro.boot.mode来设置的,而ro.boot.mode这个属性是读取/proc/cmdlinec参数,最终在import_kernel_nv函数中设置的,init.c中:

static void import_kernel_nv(char *name, int for_emulator)
{
    char *value = strchr(name, '=');
    int name_len = strlen(name);

    if (value == 0) return;
    *value++ = 0;
    if (name_len == 0) return;

    if (for_emulator) {
        /* in the emulator, export any kernel option with the
         * ro.kernel. prefix */
        char buff[PROP_NAME_MAX];
        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );

        if (len < (int)sizeof(buff))
            property_set( buff, value );
        return;
    }

    if (!strcmp(name,"qemu")) {
        strlcpy(qemu, value, sizeof(qemu));
    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {  
      //androidboot.mode=emmc  --->androidboot.是12字节
        const char *boot_prop_name = name + 12;   // boot_prop_name指向mode位置处
        char prop[PROP_NAME_MAX];
        int cnt;

        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
        if (cnt < PROP_NAME_MAX)
            property_set(prop, value);
    }
}

同样的,ro.boot.hardware和ro.boot.serialno也是在/proc/cmdline中设置通过调用import_kernel_nv函数设置的:

cat  /proc/cmdline
vmalloc=496M console=ttyFIQ0 androidboot.hardware=rk30board androidboot.console=ttyFIQ0 board.ap_has_alsa=0 init=/init 
mtdparts=rk29xxnand:0x00002000@0x00002000(uboot),0x00002000@0x00004000(misc),0x00008000@0x00006000(resource),0x00008000@0x0000e000(kernel),
0x00010000@0x00016000(boot),0x00010000@0x00026000(recovery),0x0001a000@0x00036000(backup),0x00040000@0x00050000(cache),0x00002000@0x00090000(kpanic),
0x00100000@0x00092000(system),0x00002000@0x00192000(metadata),0x00200000@0x00194000(userdata),0x00020000@0x00394000(radical_update), 
-@0x003B4000(user) storagemedia=emmc uboot_logo=0x02000000@0x7dc00000 loader.timestamp=2015-07-17_14:37:15 androidboot.serialno=11112222333344445555 <NULL> 
androidboot.mode=emmc

实际上/proc/cmdline所显示的androidboot.mode=emmc并不是parameter参数传递的,而是在kernel中指定的,这就是前文所述。位于kernel/block/partitions/rk.c文件中:

static void rkpart_bootmode_fixup(void)
{
    const char mode[] = " androidboot.mode=emmc";
    const char charger[] = " androidboot.charger.emmc=1";
    char *new_command_line;
    size_t saved_command_line_len = strlen(saved_command_line);

    if (strstr(saved_command_line, "androidboot.mode=charger")) {
        new_command_line = kzalloc(saved_command_line_len + strlen(charger) + 1, GFP_KERNEL);
        sprintf(new_command_line, "%s%s", saved_command_line, charger);
    } else {
        new_command_line = kzalloc(saved_command_line_len + strlen(mode) + 1, GFP_KERNEL);
        sprintf(new_command_line, "%s%s", saved_command_line, mode);
    }
    saved_command_line = new_command_line;
}

这里的androidboot.mode=charger是充电图标显示的模式。

本文只是简单讲述fstab,bootmode等的来龙去脉,更深层次的知识有待研究。

猜你喜欢

转载自blog.csdn.net/u014770862/article/details/54583157