续《Android sdcard存储方案(基于fuse文件系统):之一》,再聊聊基于android sdcard存储方案(基于fuse文件系统):之二,
以后有空再谈谈该方案的缺点,及优化方案。
从上面效果对比图,我们可以发现android fuse sdcard 有如下两个优点:
1、使用fuse后 /data 和 /sdcard0 是共离一块分区,这块分区的空间/data和/sdcard0 动态享用, 用户使用灵活。
2、去掉了fat32文件系统,这样也免去了一个license的风险。
Filesystem in Userspace
这里重点说明libfuse的作用:libfuse为开发者提供了接口fuse_operations开发者只需要实现这组接口,然后调用fuse初始化接口:fuse_mount()、fuse_new()、fuse_loop()即可实现一个用户空间文件系统。这样为开发多种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状态,如下图:
解决办法,就是设置一个/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; }
解决办法:
• 如果需要清除data时( 即format /data) ,不直接格式化,而采用删除方式(白名单/data/media/ )
手机出货前,厂家通常会预置一些资源文件,比如,导航地图,广告视频等。
解决方法:将预置资源编译到/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()。