首先,我要解决的是在Android 4.4上将sata硬盘挂载为sdcard权限,那么这里就要用到fuse。
先放上Android官方的配置文档镇楼:
https://source.android.com/devices/storage/config-example
关于fuse的描述可以参考下面这篇文章,
https://blog.csdn.net/hljhnu/article/details/53055695
早期的android系统没有使用fuse文件系统。后来android为了控制不同APP对文件访问的权限,使用了fuse文件系统。早期手机内置SD卡使用一个独立vfat文件系统格式的分区。使用fuse之后,将手机内置SD卡与 userdata分区合并成为一个分区。userdata分区使用ext4文件系统存储数据,访问userdata分区是直接操作ext4文件系统,而访问内置SD卡,则是先访问fuse文件系统,然后再访问ext4文件系统。
android手机使用fuse文件系统的基本方法是,创建fuse设备,并将fuse设备挂载到与内置SD卡目录关联的目录。那么,对内置SD卡的访问变成了先访问fuse文件系统,再访问ext4文件系统。fuse的内核部分创建了多个队列,其中包含一个pending队列和一个processing队列。每当有调用者对内置SD卡的系统调用时,fuse把文件访问路径转换为对ext4文件系统的操作路径,设置对应的操作码,并放入一个请求中。fuse在用户态有3个监控线程,循环地读取fuse设备。对fuse设备的读取操作在内核部分转换从pending队列读取请求,如果队列中没有请求,则对应的线程进入睡眠状态。监控线程读取到pending队列中的请求后,把请求转换为对ext4文件系统的系统调用操作。系统调用执行完成后,监控线程把执行结果写入到fuse设备,对fuse设备的写操作在内核部分转换为把结果放入processing队列。processing队列依次取出结果,返回给调用者。
android手机中sdcardfs的作用与fuse相同,也是用于控制文件访问的权限。sdcardfs的工作方式是把内置SD卡目录挂载到用于权限控制目录。对内置SD卡的系统调用,先经过sdcardfs,然后把访问路径改为ext4文件系统的真正路径,再到达ext4文件系统。ext4执行完以后,把结果返回给sdcardfs,再返回给调用者。
对比fuse和sdcardfs,对同一个文件访问,fuse需要经过6次用户态与内核态的切换,但是sdcardfs只需要经过2次切换。另外fuse在内核中有多个队列,队列中元素的出列要等带前面的元素先出列。因此单次文件访问,fuse比sdcardfs需要更多的时间。但是,不管是fuse,还是sdcardfs,对文件的单次访问,大部分情况下时间是很短的,人从感官上无法区分。而对于耗时的文件读写操作的时间来说,上述多出来的时间微不足道。而真正访问时间差异在来源于量变引起质变。当需要进行大量的文件访问时,累积产生时间差异是可以明显感觉出来的。
我们回头再来看下KK上sdcard的挂载过程。
int Volume::mountVol() {
dev_t deviceNodes[4];
int n, i, rc = 0;
char errmsg[255];
int flags = getFlags();
bool providesAsec = (flags & VOL_PROVIDES_ASEC) != 0;
// TODO: handle "bind" style mounts, for emulated storage
char decrypt_state[PROPERTY_VALUE_MAX];
char crypto_state[PROPERTY_VALUE_MAX];
char encrypt_progress[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt_state, "");
property_get("vold.encrypt_progress", encrypt_progress, "");
/* Don't try to mount the volumes if we have not yet entered the disk password
* or are in the process of encrypting.
*/
if ((getState() == Volume::State_NoMedia) ||
((!strcmp(decrypt_state, "1") || encrypt_progress[0]) && providesAsec)) {
snprintf(errmsg, sizeof(errmsg),
"Volume %s %s mount failed - no media",
getLabel(), getFuseMountpoint());
mVm->getBroadcaster()->sendBroadcast(
ResponseCode::VolumeMountFailedNoMedia,
errmsg, false);
errno = ENODEV;
return -1;
} else if (getState() != Volume::State_Idle) {
errno = EBUSY;
if (getState() == Volume::State_Pending) {
mRetryMount = true;
}
return -1;
}
if (isMountpointMounted(getMountpoint())) {
SLOGW("Volume is idle but appears to be mounted - fixing");
setState(Volume::State_Mounted);
// mCurrentlyMountedKdev = XXX
return 0;
}
n = getDeviceNodes((dev_t *) &deviceNodes, 4);
if (!n) {
SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
return -1;
}
/* If we're running encrypted, and the volume is marked as encryptable and nonremovable,
* and also marked as providing Asec storage, then we need to decrypt
* that partition, and update the volume object to point to it's new decrypted
* block device
*/
property_get("ro.crypto.state", crypto_state, "");
if (providesAsec &&
((flags & (VOL_NONREMOVABLE | VOL_ENCRYPTABLE))==(VOL_NONREMOVABLE | VOL_ENCRYPTABLE)) &&
!strcmp(crypto_state, "encrypted") && !isDecrypted()) {
char new_sys_path[MAXPATHLEN];
char nodepath[256];
int new_major, new_minor;
if (n != 1) {
/* We only expect one device node returned when mounting encryptable volumes */
SLOGE("Too many device nodes returned when mounting %d\n", getMountpoint());
return -1;
}
if (cryptfs_setup_volume(getLabel(), MAJOR(deviceNodes[0]), MINOR(deviceNodes[0]),
new_sys_path, sizeof(new_sys_path),
&new_major, &new_minor)) {
SLOGE("Cannot setup encryption mapping for %d\n", getMountpoint());
return -1;
}
/* We now have the new sysfs path for the decrypted block device, and the
* majore and minor numbers for it. So, create the device, update the
* path to the new sysfs path, and continue.
*/
snprintf(nodepath,
sizeof(nodepath), "/dev/block/vold/%d:%d",
new_major, new_minor);
if (createDeviceNode(nodepath, new_major, new_minor)) {
SLOGE("Error making device node '%s' (%s)", nodepath,
strerror(errno));
}
// Todo: Either create sys filename from nodepath, or pass in bogus path so
// vold ignores state changes on this internal device.
updateDeviceInfo(nodepath, new_major, new_minor);
/* Get the device nodes again, because they just changed */
n = getDeviceNodes((dev_t *) &deviceNodes, 4);
if (!n) {
SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
return -1;
}
}
for (i = 0; i < n; i++) {
char devicePath[255];
sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
MINOR(deviceNodes[i]));
SLOGI("%s being considered for volume %s\n", devicePath, getLabel());
errno = 0;
setState(Volume::State_Checking);
if (Fat::check(devicePath)) {
if (errno == ENODATA) {
SLOGW("%s does not contain a FAT filesystem\n", devicePath);
continue;
}
errno = EIO;
/* Badness - abort the mount */
SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
setState(Volume::State_Idle);
return -1;
}
errno = 0;
int gid;
if (Fat::doMount(devicePath, getMountpoint(), false, false, false,
AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
continue;
}
extractMetadata(devicePath);
if (providesAsec && mountAsecExternal() != 0) {
SLOGE("Failed to mount secure area (%s)", strerror(errno));
umount(getMountpoint());
setState(Volume::State_Idle);
return -1;
}
char service[64];
snprintf(service, 64, "fuse_%s", getLabel());
property_set("ctl.start", service);//这里会启动fuse_service来挂载相应的设备
setState(Volume::State_Mounted);
mCurrentlyMountedKdev = deviceNodes[i];
return 0;
}
上面的property_set("ctl.start", service)会启动下面的service来完成挂载,当然也包括U盘和sata都可以采用类似的操作。
# fusewrapped external sdcard daemon running as media_rw (1023)
service fuse_sdcard /system/bin/sdcard -u 1023 -g 1023 -d /mnt/media_rw/sdcard /storage/sdcard
class late_start
disabled
# virtual sdcard daemon running as media_rw (1023)
service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated
class late_start
chown system system /data/etc/storage.config
# fusewrapped external sdcard daemon running as media_rw (1023)
service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1
class late_start
service fuse_usbotg /system/bin/sdcard -u 1023 -g 1023 -d /mnt/media_rw/usbotg /storage/usbotg
class late_start
自此,Android 4.4上面的问题就大概解决了,我们再看下Android6.0上的内置sdcard挂载。
6.0上去掉了,类似这样的service:service fuse_sdcard /system/bin/sdcard
大概流程如下:
https://blog.csdn.net/kc58236582/article/details/50433150
Android6.0vold除了通信部分改动不大,其他基本改动很大,那我们就从头开始分析一下吧。
一、vold初始化
先看下main函数中下面这段代码
- if (!(vm = VolumeManager::Instance())) {//new 了volumemanager,构造函数中就是一些成员变量初始化
- LOG(ERROR) << "Unable to create VolumeManager";
- exit(1);
- }
- if (!(nm = NetlinkManager::Instance())) {//new 了NetlinkManager,构造函数中就是一些成员变量初始化
- LOG(ERROR) << "Unable to create NetlinkManager";
- exit(1);
- }
- if (property_get_bool("vold.debug", false)) {
- vm->setDebug(true);
- }
- cl = new CommandListener();//新建一个CommandListener
- ccl = new CryptCommandListener();
- vm->setBroadcaster((SocketListener *) cl);//CommandListener也负责和MountService通信
- nm->setBroadcaster((SocketListener *) cl);
- if (vm->start()) {//VolumManager的start方法
- PLOG(ERROR) << "Unable to start VolumeManager";
- exit(1);
- }
下面我们主要看下VolumManager的start方法:
- int VolumeManager::start() {
- // Always start from a clean slate by unmounting everything in
- // directories that we own, in case we crashed.
- unmountAll();//unmount所有的
- // Assume that we always have an emulated volume on internal
- // storage; the framework will decide if it should be mounted.
- CHECK(mInternalEmulated == nullptr);
- mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
- new android::vold::EmulatedVolume("/data/media"));
- mInternalEmulated->create();
- return 0;
- }
新建了一个EmulatedVolume,先看下构造函数
- EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
- VolumeBase(Type::kEmulated), mFusePid(0) {
- setId("emulated");
- mRawPath = rawPath;
- mLabel = "emulated";
- }
把data/media赋给了mRawPath变量
二、通知MountService emulated volume建立
接下来我们再看下EmulatedVolume的create函数,因为EmulatedVolume没有create函数,我们就看VolumeBase::create
- status_t VolumeBase::create() {
- CHECK(!mCreated);
- mCreated = true;
- status_t res = doCreate();
- notifyEvent(ResponseCode::VolumeCreated,
- StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
- setState(State::kUnmounted);
- return res;
- }
在create函数中给MountService发送VolumeCreated命令,然后将该volume设置成Unmounted状态
我们看下MountService的onEvent处理:
- case VoldResponseCode.VOLUME_CREATED: {
- final String id = cooked[1];
- final int type = Integer.parseInt(cooked[2]);
- final String diskId = TextUtils.nullIfEmpty(cooked[3]);
- final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
- final DiskInfo disk = mDisks.get(diskId);
- final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);
- mVolumes.put(id, vol);
- onVolumeCreatedLocked(vol);
- break;
- }
注意本来MountService就在addInternalVolume函数中,在mVolumes加了data目录的volume。和这个不同
我们再来看看onVolumeCreatedLocked函数
onVolumeCreatedLocked方法会发送H_VOLUME_MOUNT消息,我们就不详细说这个函数了。因为vold发送上来的type是emulated的,于是
vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
来看看这个消息的处理吧:
- case H_VOLUME_MOUNT: {
- final VolumeInfo vol = (VolumeInfo) msg.obj;
- if (isMountDisallowed(vol)) {
- Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
- break;
- }
- try {
- mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
- vol.mountUserId);
- } catch (NativeDaemonConnectorException ignored) {
- }
- break;
- }
处理是直接给vold发送的mount的命令。
下面我们看看VolumeCmd::runCommand下的这段代码是处理MountService发给来的mount命令的
- } else if (cmd == "mount" && argc > 2) {
- // mount [volId] [flags] [user]
- std::string id(argv[2]);
- auto vol = vm->findVolume(id);
- if (vol == nullptr) {
- return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
- }
- int mountFlags = (argc > 3) ? atoi(argv[3]) : 0;
- userid_t mountUserId = (argc > 4) ? atoi(argv[4]) : -1;
- vol->setMountFlags(mountFlags);
- vol->setMountUserId(mountUserId);
- int res = vol->mount();
- if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {//如果是Primary的调用volumeManager的setprimary方法
- vm->setPrimary(vol);
- }
- return sendGenericOkFail(cli, res);
- }
三、建立mnt/user下的软链接
- int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol) {
- mPrimary = vol;
- for (userid_t userId : mStartedUsers) {
- linkPrimary(userId);
- }
- return 0;
- }
遍历已经启动的user,再调用linkPrimary方法:
- int VolumeManager::linkPrimary(userid_t userId) {
- std::string source(mPrimary->getPath());
- if (mPrimary->getType() == android::vold::VolumeBase::Type::kEmulated) {
- source = StringPrintf("%s/%d", source.c_str(), userId);
- fs_prepare_dir(source.c_str(), 0755, AID_ROOT, AID_ROOT);
- }
- std::string target(StringPrintf("/mnt/user/%d/primary", userId));
- if (TEMP_FAILURE_RETRY(unlink(target.c_str()))) {
- if (errno != ENOENT) {
- SLOGW("Failed to unlink %s: %s", target.c_str(), strerror(errno));
- }
- }
- LOG(DEBUG) << "Linking " << source << " to " << target;
- if (TEMP_FAILURE_RETRY(symlink(source.c_str(), target.c_str()))) {
- SLOGW("Failed to link %s to %s: %s", source.c_str(), target.c_str(),
- strerror(errno));
- return -errno;
- }
- return 0;
- }
四、挂载emulated的volume
我们来看下volumeBase的mout函数
- 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);//设置状态,没设置一个状态都会往MountService发送
- status_t res = doMount();
- if (res == OK) {
- setState(State::kMounted);
- } else {
- setState(State::kUnmountable);
- }
- return res;
- }
我们再来看看EmulatedVolume的doMount函数
- status_t EmulatedVolume::doMount() {
- // We could have migrated storage to an adopted private volume, so always
- // call primary storage "emulated" to avoid media rescans.
- std::string label = mLabel;
- if (getMountFlags() & MountFlags::kPrimary) {
- label = "emulated";
- }
- mFuseDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());
- mFuseRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());
- mFuseWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());
- setInternalPath(mRawPath);
- setPath(StringPrintf("/storage/%s", label.c_str()));
- if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
- fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
- fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
- PLOG(ERROR) << getId() << " failed to create mount points";
- return -errno;
- }
- dev_t before = GetDevice(mFuseWrite);
- if (!(mFusePid = fork())) {
- if (execl(kFusePath, kFusePath,
- "-u", "1023", // AID_MEDIA_RW
- "-g", "1023", // AID_MEDIA_RW
- "-m",
- "-w",
- mRawPath.c_str(),
- label.c_str(),
- NULL)) {
- PLOG(ERROR) << "Failed to exec";
- }
- LOG(ERROR) << "FUSE exiting";
- _exit(1);
- }
- if (mFusePid == -1) {
- PLOG(ERROR) << getId() << " failed to fork";
- return -errno;
- }
- while (before == GetDevice(mFuseWrite)) {
- LOG(VERBOSE) << "Waiting for FUSE to spin up...";
- usleep(50000); // 50ms
- }
- return OK;
- }
- case VoldResponseCode.VOLUME_PATH_CHANGED: {
- if (cooked.length != 3) break;
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- vol.path = cooked[2];
- }
- break;
- }
- case VoldResponseCode.VOLUME_INTERNAL_PATH_CHANGED: {
- if (cooked.length != 3) break;
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- vol.internalPath = cooked[2];
- }
- break;
- }
其实InternalPath就是一个内存存储真正的路径data/media,path就是storage/emulated,而我们再看init.rc中一段
- mount none /mnt/runtime/default /storage slave bind rec
将mnt/runtime/default挂载到storage上,
- symlink /mnt/user/0/primary /mnt/runtime/default/self/primary
而对storage/emulated是利用fuse文件系统,再去读取data/media的
mount结束后最后在mount函数中setState(State::kMounted);
然后在MountService做如下处理,把mVolumes新的状态保存下来,再调用onVolumeStateChangedLocked
- case VoldResponseCode.VOLUME_STATE_CHANGED: {
- if (cooked.length != 3) break;
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- final int oldState = vol.state;
- final int newState = Integer.parseInt(cooked[2]);
- vol.state = newState;
- onVolumeStateChangedLocked(vol, oldState, newState);
- }
- break;
- }
onVolumeStateChangedLocked函数中会去调用mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);通知各个listener状态改变
小tips
使用busybox fuse -cu filename 可以查到使用当前文件的进程。
使用busybox fuse -k filename 可以杀死使用当前文件的进程。