Android存储管理涉及的三个角色包括Vold、StorageManagerService、MediaProvider.
-
Vold 主要负责接收来自驱动的外部存储设备如U盘的插拔事件,并完成挂载和卷管理功能。
-
StorageManagerService 是system server 进程的一个服务, 主要负责与Vold通信,接收来自Vold 的通知,如挂载是否成功,并发送广播通知应用。
-
MediaProvider 为App 提供访问外部存储接口及权限控制,应用访问/sdcard/Android/data /sdcard/Android/obb 目录以外的目录,都需要经过fuse文件系统,由 MediaProvider 响应这些文件访问。
-
Vold
在我们开发过程中接触到的vold管理的卷主要有两种:虚拟卷 (EmulatedVolume)、外部存储设备卷。
-
虚拟卷一般指的是真正访问目录为/data/media/的虚拟卷 ,它的主要作用是给应用进程提供一个虚拟的外部存储空间。
-
外部存储设备卷就是指比如SD卡上的卷。
vold进程启动
vold进程由init进程启动,以下是启动脚本。
/system/vold/vold.rc
service vold /system/bin/vold \
--blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
--fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
class core
ioprio be 2
writepid /dev/cpuset/foreground/tasks
shutdown critical
group root reserved_disk
vold进程启动后执行入口方法:
system/vold/main.cpp
nt main(int argc, char** argv) {
VolumeManager* vm;
NetlinkManager* nm;
mkdir("/dev/block/vold", 0755);
/* For when cryptfs checks and mounts an encrypted filesystem */
klog_set_level(6);
//创建VolumeManager兑现
/* Create our singleton managers */
if (!(vm = VolumeManager::Instance())) {
LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
}
//创建NetlinkManager
if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}
if (vm->start()) {
PLOG(ERROR) << "Unable to start VolumeManager";
exit(1);
}
//VoldNativeService是binder系统服务,名字为"vold"
ATRACE_BEGIN("VoldNativeService::start");
if (android::vold::VoldNativeService::start() != android::OK) {
LOG(ERROR) << "Unable to start VoldNativeService";
exit(1);
}
ATRACE_END();
LOG(DEBUG) << "VoldNativeService::start() completed OK";
//开始监听内核发来的uevent
ATRACE_BEGIN("NetlinkManager::start");
if (nm->start()) {
PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
}
... ...
android::IPCThreadState::self()->joinThreadPool();
LOG(INFO) << "vold shutting down";
exit(0);
}
以上main方法中涉及的主要类:
- VolumeManager
管理虚拟卷mInternalEmulatedVolumes,以及外部存储列表mDisks。
- NetlinkManager
与kernel 通信,接收kernel 发来的uevent事件,如外部存储设备插拔。
- VoldNativeService
binder服务,负责给StorageManagerService 提供远程调用接口,如mount() 等。
挂载路径对应
- 外置存储(虚拟卷):
底层目录:/data/media/userId
上层目录:storage/emulated/userId
挂载目录: /mnt/user/userId/emulated/userId
- 存储设备(U盘)
底层目录: /dev/block/vold
上层目录:storage/XXXX
挂载目录:/mnt/media_rw/XXXX
挂载流程
- 虚拟卷创建及挂载
- 虚拟卷创建
在用户创建时会创建data/media/userId 虚拟卷,创建流程:
StorageManagerService -> VoldNativeService::onUserStarted->VolumeManager::onUserStarted
void VolumeManager::createEmulatedVolumesForUser(userid_t userId) {
// Create unstacked EmulatedVolumes for the user
//以data/media/userId为raw path 创建虚拟卷
auto vol = std::shared_ptr<android::vold::VolumeBase>(
new android::vold::EmulatedVolume("/data/media", userId));
vol->setMountUserId(userId);
mInternalEmulatedVolumes.push_back(vol);
vol->create();
... ...
}
status_t VolumeBase::create() {
CHECK(!mCreated);
mCreated = true;
status_t res = doCreate();
auto listener = getListener();
//通知StorageManagerService 已经创建
if (listener) {
listener->onVolumeCreated(getId(), static_cast<int32_t>(mType), mDiskId, mPartGuid,
mMountUserId);
}
//设置状态为未挂载
setState(State::kUnmounted);
return res;
}
2. 虚拟卷挂载
binder::Status VoldNativeService::mount(
const std::string& volId, int32_t mountFlags, int32_t mountUserId,
const android::sp<android::os::IVoldMountCallback>& callback) {
ENFORCE_SYSTEM_OR_ROOT;
CHECK_ARGUMENT_ID(volId);
ACQUIRE_LOCK;
//查找对应卷
auto vol = VolumeManager::Instance()->findVolume(volId);
if (vol == nullptr) {
return error("Failed to find volume " + volId);
}
vol->setMountFlags(mountFlags);
vol->setMountUserId(mountUserId);
vol->setMountCallback(callback);
//开始挂载
int res = vol->mount();
vol->setMountCallback(nullptr);
... ...
}
/system/vold/model/VolumeBase.cpp
status_t VolumeBase::mount() {
if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
return -EBUSY;
}
setState(State::kChecking);
status_t res = doMount();
setState(res == OK ? State::kMounted : State::kUnmountable);
if (res == OK) {
doPostMount();
}
return res;
}
/system/vold/model/EmulatedVolume.cpp
status_t EmulatedVolume::doMount() {
if (isFuse && isVisible) {
LOG(INFO) << "Mounting emulated fuse volume";
android::base::unique_fd fd;
int user_id = getMountUserId();
auto volumeRoot = getRootPath();
// Make sure Android/ dirs exist for bind mounting
status_t res = PrepareAndroidDirs(volumeRoot);
if (res != OK) {
LOG(ERROR) << "Failed to prepare Android/ directories";
return res;
}
//挂载fuse
res = MountUserFuse(user_id, getInternalPath(), label, &fd);
if (res != 0) {
PLOG(ERROR) << "Failed to mount emulated fuse volume";
return res;
}
// 定义FUSE卸载的lambda函数:出错时清理FUSE挂载
mFuseMounted = true;
auto fuse_unmounter = [&]() {
LOG(INFO) << "fuse_unmounter scope_guard running";
fd.reset();
if (UnmountUserFuse(user_id, getInternalPath(), label) != OK) {
PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume";
}
mFuseMounted = false;
};
... ...
return OK;
}
MountUserFuse()方法注释比较多,这里就不贴代码了, 主要的工作包括:
- 创建fuse挂载目录 /mnt/user/userid,创建直通挂载目录/mnt/pass_through/userid
- 如果传参relative_upper_path 是 "emulated",创建符号链接,作用是应用通过统一的 /storage/emulated/userid 访问用户存储,而实际指向 FUSE 或pass_through路径
/mnt/user/userid/self/primary → /storage/emulated/userid
/mnt/pass_through/userid/self/primary → /storage/emulated/userid
-
打开驱动fuse 设备
-
fuse_fd->reset(open("/dev/fuse", O_RDWR | O_CLOEXEC));
-
-
通过 mount 系统将/dev/fuse 挂载到 /mnt/user/userid
- U盘挂载
插入U盘,vold接收到kernel 发来的uevent后,调用handleDiskAdded,
void VolumeManager::handleDiskAdded(const std::shared_ptr<android::vold::Disk>& disk) {
... ...
} else {
disk->create();
mDisks.push_back(disk);
}
}
/system/vold/model/Disk.cpp
status_t Disk::create() {
//检查是否已经创建了
CHECK(!mCreated);
mCreated = true;
//通知StorageManagerService Disk创建
auto listener = VolumeManager::Instance()->getListener();
if (listener) listener->onDiskCreated(getId(), mFlags);
if (isStub()) {
createStubVolume();
return OK;
}
readMetadata();
readPartitions();
return OK;
}
readPartitions()中调用createPublicVolume()创建 PublicVolume
void Disk::createPublicVolume(dev_t device) {
auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
if (mJustPartitioned) {
LOG(DEBUG) << "Device just partitioned; silently formatting";
vol->setSilent(true);
vol->create();
vol->format("auto");
vol->destroy();
vol->setSilent(false);
}
mVolumes.push_back(vol);
vol->setDiskId(getId());
//这里调用IVoldListener.onVolumeCreated 通知StorageManagerService卷创建
vol->create();
}
StorageManagerService收到 onVolumeCreated 后开始触发vold挂载流程,最后主要挂载流程在PublicVolume.doMount中。
status_t PublicVolume::doMount() {
... ...
//设置内核直接挂载的原始路径:/mnt/media_rw/stableName
setInternalPath(mRawPath);
if (isVisible) {
//设置应用访问路径,应用通过 Environment.getExternalStorageDirectory() 获取
setPath(StringPrintf("/storage/%s", stableName.c_str()));
} else {
setPath(mRawPath);
}
... ...
bool isFuse = base::GetBoolProperty(kPropFuse, false);
if (isFuse) {
// We need to mount FUSE *after* sdcardfs, since the FUSE daemon may depend
// on sdcardfs being up.
LOG(INFO) << "Mounting public fuse volume";
android::base::unique_fd fd;
int user_id = getMountUserId();
//挂载fuse
int result = MountUserFuse(user_id, getInternalPath(), stableName, &fd);
if (result != 0) {
LOG(ERROR) << "Failed to mount public fuse volume";
doUnmount();
return -result;
}
mFuseMounted = true;
auto callback = getMountCallback();
if (callback) {
bool is_ready = false;
//回调StorageManagerService.onVolumeChecking
callback->onVolumeChecking(std::move(fd), getPath(), getInternalPath(), &is_ready);
if (!is_ready) {
LOG(ERROR) << "Failed to complete public volume mount";
doUnmount();
return -EIO;
}
}
... ...
}
return OK;
}
- 首先设置内核直接挂载的原始路径:/mnt/media_rw/stableName和应用访问路径/storage/stableName
- MountUserFuse 作用是将fuse 挂载点/storage/XXX关联底层原始挂载点/mnt/media_rw/XXX
- fuse挂载后回调StorageManagerService.onVolumeChecking通知上层,如果上层不抛异常,则挂载完成
-
StorageManagerService
StorageManagerService是system server中的一个系统服务,主要负责:
- 与vold 通信,通知vold 用户创建、挂载等,同时接收vold 发来的信息如新增disk、挂载是否成功等
- 与MediaProvider 通信,告知volume 挂载状态,然后MediaProvider开始创建Fuse Deamon
- APP通过StorageManagerService可以获得可用的卷,StorageManagerService通过发送广播(如Intent.ACTION_MEDIA_MOUNTED)通知应用存储设备挂载状态。
-
MediaProvider
Fuse机制中的 Fuse Deamon 所在进程就是 MediaProvider, Fuse Deamon会拦截应用对/storage/emualated/userid文件操作,进行权限校验,然后往data/media 路径读写。
Fuse 原理简介
- 没有使用Fuse 机制文件写入流程
- 经过fuse 拦截后的文件写入流程
应用发起写入/storage/emualated/userid → 内核拦截(dev/fuse 挂载点)→ FUSE Daemon 接收请求 → 调用MediaProvider校验权限 → 校验成功 → 写入data/media/userid 。
挂载后 onVolumeChecking 流程
MediaProvider 权限校验流程
Fuse deamon 收到fuse 驱动发来的文件操作请求后,会调用MediaProviderWrapper::IsOpenAllowed()判断是否有权限,MediaProvider 权限校验流程如下:
aosp/vendor/ecarx/external/exfat/libexfat
-
参考文章