您的位置:首页 > 移动开发 > Android开发

android分区挂载fstab

2017-01-17 14:58 621 查看
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等的来龙去脉,更深层次的知识有待研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: