链路如下:
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挂载绑定的。