Android vold调试过程(4.4 & 6.0)

首先,我要解决的是在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函数中下面这段代码

[cpp]  view plain  copy
  1. if (!(vm = VolumeManager::Instance())) {//new 了volumemanager,构造函数中就是一些成员变量初始化  
  2.     LOG(ERROR) << "Unable to create VolumeManager";  
  3.     exit(1);  
  4. }  
  5.   
  6. if (!(nm = NetlinkManager::Instance())) {//new 了NetlinkManager,构造函数中就是一些成员变量初始化  
  7.     LOG(ERROR) << "Unable to create NetlinkManager";  
  8.     exit(1);  
  9. }  
  10.   
  11. if (property_get_bool("vold.debug"false)) {  
  12.     vm->setDebug(true);  
  13. }  
  14.   
  15. cl = new CommandListener();//新建一个CommandListener  
  16. ccl = new CryptCommandListener();  
  17. vm->setBroadcaster((SocketListener *) cl);//CommandListener也负责和MountService通信  
  18. nm->setBroadcaster((SocketListener *) cl);  
  19.   
  20. if (vm->start()) {//VolumManager的start方法  
  21.     PLOG(ERROR) << "Unable to start VolumeManager";  
  22.     exit(1);  
  23. }  

下面我们主要看下VolumManager的start方法:

[cpp]  view plain  copy
  1. int VolumeManager::start() {  
  2.     // Always start from a clean slate by unmounting everything in  
  3.     // directories that we own, in case we crashed.  
  4.     unmountAll();//unmount所有的  
  5.   
  6.     // Assume that we always have an emulated volume on internal  
  7.     // storage; the framework will decide if it should be mounted.  
  8.     CHECK(mInternalEmulated == nullptr);  
  9.     mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(  
  10.             new android::vold::EmulatedVolume("/data/media"));  
  11.     mInternalEmulated->create();  
  12.   
  13.     return 0;  
  14. }  

新建了一个EmulatedVolume,先看下构造函数

[cpp]  view plain  copy
  1. EmulatedVolume::EmulatedVolume(const std::string& rawPath) :  
  2.         VolumeBase(Type::kEmulated), mFusePid(0) {  
  3.     setId("emulated");  
  4.     mRawPath = rawPath;  
  5.     mLabel = "emulated";  
  6. }  

把data/media赋给了mRawPath变量


二、通知MountService emulated volume建立

接下来我们再看下EmulatedVolume的create函数,因为EmulatedVolume没有create函数,我们就看VolumeBase::create

[cpp]  view plain  copy
  1. status_t VolumeBase::create() {  
  2.     CHECK(!mCreated);  
  3.   
  4.     mCreated = true;  
  5.     status_t res = doCreate();  
  6.     notifyEvent(ResponseCode::VolumeCreated,  
  7.             StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));  
  8.     setState(State::kUnmounted);  
  9.     return res;  
  10. }  

在create函数中给MountService发送VolumeCreated命令,然后将该volume设置成Unmounted状态

我们看下MountService的onEvent处理:

[java]  view plain  copy
  1. case VoldResponseCode.VOLUME_CREATED: {  
  2.     final String id = cooked[1];  
  3.     final int type = Integer.parseInt(cooked[2]);  
  4.     final String diskId = TextUtils.nullIfEmpty(cooked[3]);  
  5.     final String partGuid = TextUtils.nullIfEmpty(cooked[4]);  
  6.     final DiskInfo disk = mDisks.get(diskId);  
  7.     final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);  
  8.     mVolumes.put(id, vol);  
  9.     onVolumeCreatedLocked(vol);  
  10.     break;  
  11. }  

注意本来MountService就在addInternalVolume函数中,在mVolumes加了data目录的volume。和这个不同
我们再来看看onVolumeCreatedLocked函数

onVolumeCreatedLocked方法会发送H_VOLUME_MOUNT消息,我们就不详细说这个函数了。因为vold发送上来的type是emulated的,于是

vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;

来看看这个消息的处理吧:

[java]  view plain  copy
  1. case H_VOLUME_MOUNT: {  
  2.     final VolumeInfo vol = (VolumeInfo) msg.obj;  
  3.     if (isMountDisallowed(vol)) {  
  4.         Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");  
  5.         break;  
  6.     }  
  7.     try {  
  8.         mConnector.execute("volume""mount", vol.id, vol.mountFlags,  
  9.                 vol.mountUserId);  
  10.     } catch (NativeDaemonConnectorException ignored) {  
  11.     }  
  12.     break;  
  13. }  

处理是直接给vold发送的mount的命令。

下面我们看看VolumeCmd::runCommand下的这段代码是处理MountService发给来的mount命令的

[cpp]  view plain  copy
  1. else if (cmd == "mount" && argc > 2) {  
  2.     // mount [volId] [flags] [user]  
  3.     std::string id(argv[2]);  
  4.     auto vol = vm->findVolume(id);  
  5.     if (vol == nullptr) {  
  6.         return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume"false);  
  7.     }  
  8.   
  9.     int mountFlags = (argc > 3) ? atoi(argv[3]) : 0;  
  10.     userid_t mountUserId = (argc > 4) ? atoi(argv[4]) : -1;  
  11.   
  12.     vol->setMountFlags(mountFlags);  
  13.     vol->setMountUserId(mountUserId);  
  14.   
  15.     int res = vol->mount();  
  16.     if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {//如果是Primary的调用volumeManager的setprimary方法  
  17.         vm->setPrimary(vol);  
  18.     }  
  19.     return sendGenericOkFail(cli, res);  
  20.   
  21. }   


三、建立mnt/user下的软链接


[java]  view plain  copy
  1. int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol) {  
  2.     mPrimary = vol;  
  3.     for (userid_t userId : mStartedUsers) {  
  4.         linkPrimary(userId);  
  5.     }  
  6.     return 0;  
  7. }  

遍历已经启动的user,再调用linkPrimary方法:

[cpp]  view plain  copy
  1. int VolumeManager::linkPrimary(userid_t userId) {  
  2.     std::string source(mPrimary->getPath());  
  3.     if (mPrimary->getType() == android::vold::VolumeBase::Type::kEmulated) {  
  4.         source = StringPrintf("%s/%d", source.c_str(), userId);  
  5.         fs_prepare_dir(source.c_str(), 0755, AID_ROOT, AID_ROOT);  
  6.     }  
  7.   
  8.     std::string target(StringPrintf("/mnt/user/%d/primary", userId));  
  9.     if (TEMP_FAILURE_RETRY(unlink(target.c_str()))) {  
  10.         if (errno != ENOENT) {  
  11.             SLOGW("Failed to unlink %s: %s", target.c_str(), strerror(errno));  
  12.         }  
  13.     }  
  14.     LOG(DEBUG) << "Linking " << source << " to " << target;  
  15.     if (TEMP_FAILURE_RETRY(symlink(source.c_str(), target.c_str()))) {  
  16.         SLOGW("Failed to link %s to %s: %s", source.c_str(), target.c_str(),  
  17.                 strerror(errno));  
  18.         return -errno;  
  19.     }  
  20.     return 0;  
  21. }  
上面这段代码就是把storage/emulated/0/ 创建软链接到mnt/user/0/primary

四、挂载emulated的volume


我们来看下volumeBase的mout函数

[cpp]  view plain  copy
  1. status_t VolumeBase::mount() {  
  2.     if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {  
  3.         LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";  
  4.         return -EBUSY;  
  5.     }  
  6.   
  7.     setState(State::kChecking);//设置状态,没设置一个状态都会往MountService发送  
  8.     status_t res = doMount();  
  9.     if (res == OK) {  
  10.         setState(State::kMounted);  
  11.     } else {  
  12.         setState(State::kUnmountable);  
  13.     }  
  14.   
  15.     return res;  
  16. }  

我们再来看看EmulatedVolume的doMount函数

[cpp]  view plain  copy
  1. status_t EmulatedVolume::doMount() {  
  2.     // We could have migrated storage to an adopted private volume, so always  
  3.     // call primary storage "emulated" to avoid media rescans.  
  4.     std::string label = mLabel;  
  5.     if (getMountFlags() & MountFlags::kPrimary) {  
  6.         label = "emulated";  
  7.     }  
  8.   
  9.     mFuseDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());  
  10.     mFuseRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());  
  11.     mFuseWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());  
  12.   
  13.     setInternalPath(mRawPath);  
  14.     setPath(StringPrintf("/storage/%s", label.c_str()));  
  15.   
  16.     if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||  
  17.             fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||  
  18.             fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {  
  19.         PLOG(ERROR) << getId() << " failed to create mount points";  
  20.         return -errno;  
  21.     }  
  22.   
  23.     dev_t before = GetDevice(mFuseWrite);  
  24.   
  25.     if (!(mFusePid = fork())) {  
  26.         if (execl(kFusePath, kFusePath,  
  27.                 "-u""1023"// AID_MEDIA_RW  
  28.                 "-g""1023"// AID_MEDIA_RW  
  29.                 "-m",  
  30.                 "-w",  
  31.                 mRawPath.c_str(),  
  32.                 label.c_str(),  
  33.                 NULL)) {  
  34.             PLOG(ERROR) << "Failed to exec";  
  35.         }  
  36.   
  37.         LOG(ERROR) << "FUSE exiting";  
  38.         _exit(1);  
  39.     }  
  40.   
  41.     if (mFusePid == -1) {  
  42.         PLOG(ERROR) << getId() << " failed to fork";  
  43.         return -errno;  
  44.     }  
  45.   
  46.     while (before == GetDevice(mFuseWrite)) {  
  47.         LOG(VERBOSE) << "Waiting for FUSE to spin up...";  
  48.         usleep(50000); // 50ms  
  49.     }  
  50.   
  51.     return OK;  
  52. }  
这个函数中先设置了setInternalPath,setPath,这两个函数最终都会通知MountService,在下面两个地方进行处理

[java]  view plain  copy
  1. case VoldResponseCode.VOLUME_PATH_CHANGED: {  
  2.     if (cooked.length != 3break;  
  3.     final VolumeInfo vol = mVolumes.get(cooked[1]);  
  4.     if (vol != null) {  
  5.         vol.path = cooked[2];  
  6.     }  
  7.     break;  
  8. }  
  9. case VoldResponseCode.VOLUME_INTERNAL_PATH_CHANGED: {  
  10.     if (cooked.length != 3break;  
  11.     final VolumeInfo vol = mVolumes.get(cooked[1]);  
  12.     if (vol != null) {  
  13.         vol.internalPath = cooked[2];  
  14.     }  
  15.     break;  
  16. }  

其实InternalPath就是一个内存存储真正的路径data/media,path就是storage/emulated,而我们再看init.rc中一段

[html]  view plain  copy
  1. mount none /mnt/runtime/default /storage slave bind rec  

将mnt/runtime/default挂载到storage上,

[html]  view plain  copy
  1. symlink /mnt/user/0/primary /mnt/runtime/default/self/primary  
init.rc中还有上面一个软链接,也就是说storage/self/primary也是/mnt/user/0/primary 的一个软链接

而对storage/emulated是利用fuse文件系统,再去读取data/media的
mount结束后最后在mount函数中setState(State::kMounted);

然后在MountService做如下处理,把mVolumes新的状态保存下来,再调用onVolumeStateChangedLocked

[java]  view plain  copy
  1. case VoldResponseCode.VOLUME_STATE_CHANGED: {  
  2.     if (cooked.length != 3break;  
  3.     final VolumeInfo vol = mVolumes.get(cooked[1]);  
  4.     if (vol != null) {  
  5.         final int oldState = vol.state;  
  6.         final int newState = Integer.parseInt(cooked[2]);  
  7.         vol.state = newState;  
  8.         onVolumeStateChangedLocked(vol, oldState, newState);  
  9.     }  
  10.     break;  
  11. }  

onVolumeStateChangedLocked函数中会去调用mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);通知各个listener状态改变



猜你喜欢

转载自blog.csdn.net/raykwok1150/article/details/80063351