android9.0内部储存链路

链路如下:

storage/sdcard0--->/sdcard
/sdcard -> /storage/self/primary
/storage/self/primary -> /mnt/user/0/primary
 /mnt/user/0/primary -> /storage/emulated/0

最终都是链接到/storage/emulated/0目录下。那么代码逻辑是如何链接的?

第一步:/sdcard -> /storage/self/primary

在system//core/rootdir/init.rc代码中

		#创建链接
118     # Symlink to keep legacy apps working in multi-user world
119     symlink /storage/self/primary /sdcard
120     symlink /storage/self/primary /mnt/sdcard
121     symlink /mnt/user/0/primary /mnt/runtime/default/self/primary

在系统的时候init.rc会被解析,并最终调用代码中的do_symlink接口

static Result<Success> do_symlink(const BuiltinArguments& args) {
    if (MakeSymlink(args[1], args[2]) < 0) {
        // The symlink builtin is often used to create symlinks for older devices to be backwards
        // compatible with new paths, therefore we skip reporting this error.
        if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
            return Success();
        }
       	//注意这里的的log是不会打印出来的,因为向后兼容,难免有一些链接不到,所以安卓就跳过链接出错的log
        return ErrnoError() << "symlink() failed";
    }
    return Success();
}
static int MakeSymlink(const std::string& target, const std::string& linkpath) {
    std::string secontext;
    // Passing 0 for mode should work.
    if (SelabelLookupFileContext(linkpath, 0, &secontext) && !secontext.empty()) {
        setfscreatecon(secontext.c_str());
    }

    int rc = symlink(target.c_str(), linkpath.c_str());

    if (!secontext.empty()) {
        int save_errno = errno;
        setfscreatecon(nullptr);
        errno = save_errno;
    }

    return rc;
}

最终会调用symlink接口来完成,这里注意一个点就是symlink进行符号连接时,当目标target不存在时也会连接上并且不会报错,因为一开始/storage/self/primary在init.rc阶段并不存在,但是后续该目录被链接或者被创建的时候,/sdcard便自动能访问/storage/self/primary。

第二步:/storage/self/primary -> /mnt/user/0/primary

在init.rc当中,会有如下的初始化链接。
/system/core/rootdir/init.rc

  symlink /mnt/user/0/primary /mnt/runtime/default/self/primary
  .......
  on post-fs
	......
    # Mount default storage into root namespace
    mount none /mnt/runtime/default /storage bind rec

这里因为/mnt/runtime/default/self/primary链接/mnt/user/0/primary,又有/mnt/runtime/default与/storage/相互挂载绑定,那么/storage/self/primary自然也就链接上了/mnt/user/0/primary。
注意:一些博客说/storage/self/primary是通过如下函数的mount来链接/mnt/user/0/primary的,但认真查看代码部分,改部分逻辑是/mnt/user/0挂载到/storage/self/,并不是/storage/self/primary链接到/mnt/user/0/primary。本人尝试过将其注释掉都可以正常/storage/self/primary链接到/mnt/user/0/primary,因此更加能证明此处代码逻辑并不是/storage/self/primary链接到/mnt/user/0/primary。但是如果注释点上面的symlink /mnt/user/0/primary /mnt/runtime/default/self/primary,那么就会出现/storage/self/primary链接不到到/mnt/user/0/primary,因此更能证明上面才是主要的链接逻辑。

static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
    // Mount user-specific symlink helper into place
    userid_t user_id = multiuser_get_user_id(uid);
    const String8 userSource(String8::format("/mnt/user/%d", user_id));
    if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
        *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
        return false;
    }

	/*该部分逻辑是/mnt/user/0挂载到/storage/self/,并不是/storage/self/primary链接到/mnt/user/0/primary*/
    if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
            NULL, MS_BIND, NULL)) == -1) {
        *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
                                  userSource.string(),
                                  strerror(errno));
        ALOGE("lzte mount failed /storage/self mount_mode=%d,user_id=%d",mount_mode,user_id);
        return false;
    }

    return true;
}

第三步: /mnt/user/0/primary -> /storage/emulated/0

这一步的链接是在系统启动后,通过AMS调用usercontroller类进行user 0 的unlock动作会通知到各个系统服务进行onUnlockUser,那么StorageManagerService.java最终也会调用到onUnlockUser,
frameworks/base/services/core/java/com/android/server/StorageManagerService.java

  private void onUnlockUser(int userId) {
        Slog.d(TAG, "onUnlockUser " + userId);

        // We purposefully block here to make sure that user-specific
        // staging area is ready so it's ready for zygote-forked apps to
        // bind mount against.
        try {
            mVold.onUserStarted(userId);//调用底层的Vold.onUserStarted
            mStoraged.onUserStarted(userId);
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
        ......
}

通过aidl调用到底层onUserStarted
system/vold/VoldNativeService.cpp

binder::Status VoldNativeService::onUserStarted(int32_t userId) {
    ENFORCE_UID(AID_SYSTEM);
    ACQUIRE_LOCK;

    return translate(VolumeManager::Instance()->onUserStarted(userId));
}
int VolumeManager::onUserStarted(userid_t userId) {
	......
    mStartedUsers.insert(userId);
    if (mPrimary) {
        linkPrimary(userId);
    }
    return 0;

最终判断mPrimary为真的时候,进行linkPrimary。将/storage/self/primary -> /mnt/user/0/primary。

int VolumeManager::linkPrimary(userid_t userId) {
 	......
    std::string target(StringPrintf("/mnt/user/%d/primary", userId));
    if (TEMP_FAILURE_RETRY(unlink(target.c_str()))) {
        if (errno != ENOENT) {
            PLOG(WARNING) << "Failed to unlink " << target;
        }
    }
    //进行/storage/self/primary -> /mnt/user/0/primary链接
    LOG(DEBUG) << "Linking " << source << " to " << target;
    if (TEMP_FAILURE_RETRY(symlink(source.c_str(), target.c_str()))) {
        PLOG(WARNING) << "Failed to link";
        return -errno;
    }
    return 0;
}

上面说道只有mPrimary为真的情况下,才会进行linkPrimary链接,那么什么时候mPrimary为真?只有在/storage/emulated类型被设备被mount的时候才会设置。

binder::Status VoldNativeService::mount(const std::string& volId, int32_t mountFlags,
        int32_t mountUserId) {
        ......
    int res = vol->mount();
    //emulated的挂载flag是MOUNT_FLAG_PRIMARY
    if ((mountFlags & MOUNT_FLAG_PRIMARY) != 0) {
        VolumeManager::Instance()->setPrimary(vol);//设置mPrimary
    }
    return translate(res);
}

第四步:/storage/emulated/0如何被创建

我们知道厂商当中并没有/storage这个分区,而且/data/meida/0下的数据跟/storage/emulaated/0的数据是一样的,因此我们猜测/storage/emulated/0跟data/meida/0肯定存在某种联系。通过mount命令查看

/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal)
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal)

并在init.rc中在on post-fs阶段有
system/core/rootdir/init.rc

on post-fs
	......
    # Mount default storage into root namespace
    mount none /mnt/runtime/default /storage bind rec

从上面我们就能得到/data/media跟/mnt/runtime/default挂载绑定在一块了,而在init.rc中/mnt/runtime/default又跟/storage挂载绑定在一块了,所以这里/storage/emulated/0也就跟/data/media/0绑定在一块了。

还有一个疑问?/data/media是如何/mnt/runtime/default/emulated挂载绑定的?原来VolumeManager::start() 在启动时候挂载创建的。代码如下:
system/vold/VolumeManager.cpp

int VolumeManager::start() {

    // Assume that we always have an emulated volume on internal
    // storage; the framework will decide if it should be mounted.
    CHECK(mInternalEmulated == nullptr);
#ifdef MTK_SHARED_SDCARD
    LOG(VERBOSE) << VOLD_LOG_TAG<< "MTK_SHARED_SDCARD, use /data/media as emulated volume!";
    mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
        new android::vold::EmulatedVolume("/data/media"));
#else
    LOG(VERBOSE) << VOLD_LOG_TAG << "Not MTK_SHARED_SDCARD, use /mnt/media_rw/internal_sdcard as emulated volume!";
        mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/mnt/media_rw/internal_sdcard"));
#endif
    mInternalEmulated->create();

    // Consider creating a virtual disk
    updateVirtualDisk();

    return 0;
}

这里mtk平台一般都会打开MTK_SHARED_SDCARD这个宏,因为这是把内部储存跟/data分区功用一个分区合并。
这里看到创建EmulatedVolume对象,并把挂载路径为/data/media,然后调用的时候mInternalEmulated->create()会通过listener->onVolumeCreated()回调到上层StorageManagerService,上层StorageManagerService再下发挂载命令进行 EmulatedVolume::doMount() 挂载。这里挂载的流程可以参考我的另外一篇博客:https://blog.csdn.net/Ian22l/article/details/105365234

通知上层StorageManagerService代码:
system/vold/model/VolumeBase.cpp

status_t VolumeBase::create() {
    CHECK(!mCreated);

    mCreated = true;
    status_t res = doCreate();

    auto listener = getListener();
    //通知上层StorageManagerService的onVolumeCreated:
    if (listener) listener->onVolumeCreated(getId(),
            static_cast<int32_t>(mType), mDiskId, mPartGuid);

    setState(State::kUnmounted);
    return res;
}
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";
    }
    
    //这里就是我们上面说的/mnt/runtime/default路径
    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",
                "-G",
                "-i",
                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;
    }

    nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
    while (before == GetDevice(mFuseWrite)) {
        LOG(VERBOSE) << "Waiting for FUSE to spin up...";
        usleep(50000); // 50ms

        nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME);
        if (nanoseconds_to_milliseconds(now - start) > 5000) {
            LOG(WARNING) << "Timed out while waiting for FUSE to spin up";
            return -ETIMEDOUT;
        }
    }
    /* sdcardfs will have exited already. FUSE will still be running */
    if (TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, WNOHANG)) == mFusePid)
        mFusePid = 0;

    return OK;
}

因此这里就能看出我们的/data/media是通过 EmulatedVolume::doMount跟/mnt/runtime/default/emualted挂载绑定的。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值