android sdcard存储方案二(基于fuse文件系统)



续《android sdcard存储方案(基于fuse文件系统):之一》,再聊聊基于android sdcard存储方案(基于fuse文件系统):之二,
以后有空再谈谈该方案的缺点,及优化方案。

一、android GB 及JB、KK版本内置sdcard效果对比图


从上面效果对比图,我们可以发现android fuse sdcard 有如下两个优点:
1、使用fuse后 /data 和  /sdcard0 是共离一块分区,这块分区的空间/data和/sdcard0 动态享用, 用户使用灵活。
2、去掉了fat32文件系统,这样也免去了一个license的风险。

二、fuse的标准工作流程图
from:  http://en.wikipedia.org/wiki/Filesystem_in_Userspace
Filesystem in Userspace
FUSE structure.svg
这里重点说明libfuse的作用:libfuse为开发者提供了接口fuse_operations开发者只需要实现这组接口,然后调用fuse初始化接口:fuse_mount()、fuse_new()、fuse_loop()即可实现一个用户空间文件系统。这样为开发多种fuse文件系统带来很多方便。

三、android fuse sdcard架构图及source code
1、source code:
\system\core\sdcard\
\frameworks\base\cmds\installd\    JB
./frameworks/native/cmds/installd/   KK
\kernel\fs\fuse\

2、fuse操作流程图:


android fuse sdcard的操作流程说明:
步骤1:黑色箭头所示,app通过fuse向sdcard dameon 发出操作请求
步骤2:红色箭头所示:sdcard damon 实际完成操作,比如此例:通过vfs、ext4向实际存储器操作
步骤3:蓝色箭头所示:sdcard damon通过fuse向app反馈操作结果
android fuse sdcard  流程和标准fuse流程图最大不同点在于:
android没有直接移植标准的libfuse,而是重写了相关代码,将libfuse的功能集成到sdcard dameon。
这样做的我能想到的好处,可能就是函数调用的层次少了一些,可能效率会好点。
一直想不明白android为什么不移植libfuse ?

3、fuse sdcard 的mount状态,如下图:



四、使用fuse sdcard 带来的一些问题及解决方法
1、/data和/sdcard 动态占用空间,如果用户通过/sdcard将整个分区填满,则会导致系统无法启动。
时,仅允许系统写入,不允许普通app写
  解决办法,就是设置一个/sdcard/可用的上限,不至于导致系统崩溃至无法启用。
 具体可以参考mtk修改的代码:\kernel\fs\fuse\inode.c (LIMIT_SDCARD_SIZE包宏处)
static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
{
stbuf->f_type    = FUSE_SUPER_MAGIC;
stbuf->f_bsize   = attr->bsize;
stbuf->f_frsize  = attr->frsize;
stbuf->f_blocks  = attr->blocks;
stbuf->f_bfree   = attr->bfree;
stbuf->f_bavail  = attr->bavail;
stbuf->f_files   = attr->files;
stbuf->f_ffree   = attr->ffree;
stbuf->f_namelen = attr->namelen;
#ifdef LIMIT_SDCARD_SIZE
stbuf->f_blocks  -= (u32)data_free_size_th/attr->bsize;

if(stbuf->f_bfree < ((u32)data_free_size_th/attr->bsize)){
stbuf->f_bfree = 0;
}else{
stbuf->f_bfree-= (u32)data_free_size_th/attr->bsize;
}
if(stbuf->f_bavail < ((u32)data_free_size_th/attr->bsize)){
stbuf->f_bavail = 0;
}else{
stbuf->f_bavail-= (u32)data_free_size_th/attr->bsize;
}
#endif
/* fsid is left zero */
}

注:
a. Google default mtp Mtpreserved frameworks/base/services/java/com/android/server/MountService.java270 private static final int MTP_RESERVE_SPACE = 10; field in class:MountService 2425 mtpReserve = MTP_RESERVE_SPACE; 4425 MTP_RESERVE_SPACE,
b.  mtk fuse sdcard size limite
system/core/sdcard/sdcard.c      //LIMIT_SDCARD_SIZE     

mtk新版本软件将kernel fuse 中的保护转换到sdcard service ,参考如下代码:
static int handle_write(struct fuse* fuse, struct fuse_handler* handler,
        const struct fuse_in_header* hdr, const struct fuse_write_in* req,
        const void* buffer)
{
    struct fuse_write_out out;
    struct handle *h = id_to_ptr(req->fh);
    int res;


    TRACE("[%d] WRITE %p(%d) %u@%llu\n", handler->token,
            h, h->fd, req->size, req->offset);
#ifdef LIMIT_SDCARD_SIZE
if(!strncmp( fuse->root.name,"/data/media",fuse->root.namelen)){
//LOG("[fuse_debug] fuse.free_size 1 = %lld,root name =%s \n",fuse->free_size,fuse->root.name);
pthread_mutex_lock(&fuse->lock);
fuse->free_size -=req->size;
pthread_mutex_unlock(&fuse->lock);
//LOG("[fuse_debug] fuse.free_size 2 = %lld\n",fuse->free_size);
if(fuse->free_size <= internal_sdcard_free_size_threshold){
struct statfs stat;
    if (statfs(fuse->root.name, &stat) < 0) {
       ERROR("get %s fs status fail \n",fuse->root.name);
fuse->free_size =0;
return -errno;
    }else{
    pthread_mutex_lock(&fuse->lock);
fuse->free_size = stat.f_bfree*stat.f_bsize;
pthread_mutex_unlock(&fuse->lock);
    }
errno = ENOSPC;
LOG("[fuse_debug] Oops fuse.free_size = %lld, less than internal sdcard free size threshold ,no space for write!!!!\n",fuse->free_size);
return -errno;
}
}
#endif


    res = pwrite64(h->fd, buffer, req->size, req->offset);
    if (res < 0) {
        return -errno;
    }
    out.size = res;
    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
    return NO_STATUS;
}

2、清除用户数据或者recovey、ota等涉及到要format /data 目录时,/storage/sdcard0 的数据也会被清空掉。
 解决办法:
       •如果需要清除data时(即format /data),不直接格式化,而采用删除方式(白名单/data/media/)

3、不同size emmc 的兼容:同一款手机,可能需要兼容多个size emmc,比如16G版本,32G版本。
 
  build阶段,userdata.img被如下BoardConfig.mk指定固定的分区大小。
  device/vendor/product/BoardConfig.mk   :   BOARD_USERDATAIMAGE_PARTITION_SIZE := 1073741824
  但项目兼容不同emmc size,“/data”分区的大小是希望不同的,这样才能让用户使用到不同大小的存储空间。
  解决方法:利用ext4 resize功能,第一次开机自动调整“/data”分区的大小。
   source code :\external\e2fsprogs\resize
   http://www.ibm.com/developerworks/cn/linux/l-cn-ext4resize/

   后面再专门写一篇文章说明resize功能的实际使用。

4、厂家的预置资源文件,如何导入到内置sdcard?
     手机出货前,厂家通常会预置一些资源文件,比如,导航地图,广告视频等。
    解决方法:将预置资源编译到/data/media/目录下,系统第一次启动时installd进程会自动将/data/media/目录的东西移到/data/media/0 目录,即sdcard根目录可见该预置资源。
   见如下代码:frameworks\base\cmds\installd\installd.c
           KK : ./frameworks/native/cmds/installd/installd.c
 int initialize_directories() {
    int res = -1;
    // Read current filesystem layout version to handle upgrade paths
    char version_path[PATH_MAX];
    snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
    int oldVersion;
    if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
        oldVersion = 0;
    }
    int version = oldVersion;
        // /data/media.tmp
            char media_tmp_dir[PATH_MAX];
            snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path);


            // Only copy when upgrade not already in progress  
            if (access(media_tmp_dir, F_OK) == -1) {
                if (rename(android_media_dir.path, media_tmp_dir) == -1) {              //将/data/media目录名切换成 /data/media.tmp
                    ALOGE("Failed to move legacy media path: %s", strerror(errno));
                    goto fail;
                }
            }
            // Create /data/media again
            if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {   // 重新创建/data/media目录
                goto fail;
            }
            // /data/media/0
            char owner_media_dir[PATH_MAX];
            snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);               
            // Move any owner data into place
            if (access(media_tmp_dir, F_OK) == 0) {
                if (rename(media_tmp_dir, owner_media_dir) == -1) {     //将 /data/media.tmp目录切换成/data/media/0目录名
                    ALOGE("Failed to move owner media path: %s", strerror(errno));
                    goto fail;
                }
            }
      version = 2;
         // Persist layout version if changed
    if (version != oldVersion) {
        if (fs_write_atomic_int(version_path, version) == -1) {    //写2到文件/data/.layout_version,作为上面rename操作成功的标识
            ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
            goto fail;
        }
    }
    // Success!
    res = 0;
   }
 通过上面的方法,确实可以将预置资源导入到sdcard,但在实际大量生产中,发现一个新问题:
预置资源可能会被移到/sdcard/0/ 目录(即/data/media/0/0),多了一级0目录。
为什么会发生这种问题呢? 经过长时间分析,应该是因为上面的代码稳定性极度依赖于函数fs_write_atomic_int()的原子操作性。
//下面看fs_write_atomic_int函数实现细节,
int fs_write_atomic_int(const char* path, int value) {
    char temp[PATH_MAX];
    if (snprintf(temp, PATH_MAX, "%s.XXXXXX", path) >= PATH_MAX) {
        ALOGE("Path too long");
        return -1;
    }
    int fd = TEMP_FAILURE_RETRY(mkstemp(temp));  //mkstemp没有同步效果,mkostemp android又不支持。

    if (fd == -1) {
        ALOGE("Failed to open %s: %s", temp, strerror(errno));
        return -1;
    }
    char buf[BUF_SIZE];
    int len = snprintf(buf, BUF_SIZE, "%d", value) + 1;
    if (len > BUF_SIZE) {
        ALOGE("Value %d too large: %s", value, strerror(errno));
        goto fail;
    }
    if (TEMP_FAILURE_RETRY(write(fd, buf, len)) < len) {    // write 不同步
        ALOGE("Failed to write %s: %s", temp, strerror(errno));
        goto fail;
    }
    if (close(fd) == -1) {
        ALOGE("Failed to close %s: %s", temp, strerror(errno));
        goto fail_closed;
    }
    if (rename(temp, path) == -1) {   //rename 不同步
        ALOGE("Failed to rename %s to %s: %s", temp, path, strerror(errno));
        goto fail_closed;
    }
    return 0;
fail:
    close(fd);
fail_closed:
    unlink(temp);
    return -1;
}
我理解的原子操作函数,至少会有下面几个特点:
1、短小精悍
2、不可以被打断
3、函数执行成功就等于操作的内容成功。
但实际上,fs_write_atomic_int()函数根本达不到原子操作效果。因为它调用很多文件系统接口,
而文件系统接口,write,rename等都是异步操作的。因此该函数无法满足上面原子操作函数的第3点特性。
如果解决该问题呢? 1、取消多用户   2、fs_write_atomic_int()函数后面添加sync()。
5、fuse sdcard对开机速度的影响
    由于data和sdcard共享分区后, /data分区变大了很多,特别是32G、64G等大容量emmc 。
    这样系统启动时,调用如下命令,fs check花的时间就会长很多。
    •exec /sbin/e2fsck -pfD /emmc@usrdata 

  解决方法: 正常开机,不进行完整的check流程。 仅异常开机才进行完整fs check 。类似于PC 。

6、fuse sdcard  读写性能
 
   从上面分析可知fuse sdcard 其实就是将"/data"下的“/data/media/” 映射到/storage/sdcard0目录。
  因此,最理想情况是希望 "/storage/sdcard0" 的读、写性能可以达到“/data” 目录的读、写性能。
  可实际上,由于以后两点原因,
    – 因为多了很多user/kernel space中的message 传递, flow 拉长
     – 多了copy_from_user() and copy_to_user()  的overhead   
导致读、写性能会有20%左右,甚至更多的性能drop,这是fuse的 设计先天缺限, 各位有什么好的解决方法呢?
==============>太有挑战性了,后续将作专题研究。

dd命令测试“/data” 目录和 “/storage/sdcard0/”的写性能对比数据如下:
//  "/data" 目录的写速率:
=== 128k ===
0.1311 MB in 0.0221 secs, 5.9314 MB/sec
=== 256k ===
0.2621 MB in 0.0294 secs, 8.9234 MB/sec
=== 512k ===
0.5243 MB in 0.0422 secs, 12.4271 MB/sec
=== 1m ===
1.0486 MB in 0.0674 secs, 15.5487 MB/sec
=== 2m ===
2.0972 MB in 0.0986 secs, 21.2602 MB/sec
=== 4m ===
4.1943 MB in 0.1833 secs, 22.8776 MB/sec
=== 8m ===
8.3886 MB in 0.3600 secs, 23.2996 MB/sec
=== 16m ===
16.7772 MB in 0.6883 secs, 24.3763 MB/sec
=== 32m ===
33.5544 MB in 1.0898 secs, 30.7905 MB/sec
=== 64m ===
67.1089 MB in 1.7921 secs, 37.4462 MB/sec
=== 128m ===
134.2177 MB in 3.1874 secs, 42.1089 MB/sec
=== 256m ===
268.4355 MB in 5.9758 secs, 44.9204 MB/sec

// "/storage/sdcard0" 目录的写速率:

=== 128k ===
0.1311 MB in 0.0283 secs, 4.6389 MB/sec
=== 256k ===
0.2621 MB in 0.0354 secs, 7.4048 MB/sec
=== 512k ===
0.5243 MB in 0.0611 secs, 8.5817 MB/sec
=== 1m ===
1.0486 MB in 0.1076 secs, 9.7411 MB/sec
=== 2m ===
2.0972 MB in 0.2182 secs, 9.6119 MB/sec
=== 4m ===
4.1943 MB in 0.4399 secs, 9.5354 MB/sec
=== 8m ===
8.3886 MB in 0.8618 secs, 9.7334 MB/sec
=== 16m ===
16.7772 MB in 1.6788 secs, 9.9935 MB/sec
=== 32m ===
33.5544 MB in 3.0061 secs, 11.1621 MB/sec
=== 64m ===
67.1089 MB in 5.5598 secs, 12.0704 MB/sec
=== 128m ===
134.2177 MB in 11.1888 secs, 11.9958 MB/sec
=== 256m ===
268.4355 MB in 22.4747 secs, 11.9439 MB/sec

你可能感兴趣的:(android存储)