在Android K和L版本里面,zygote会将sdcard指向/storage/emulated/0,而非从init启动的bin程序识别的/storage/emulated/legacy,也就是说如果将sdcard的路径传递给二进制服务的时候,必须要进行相应的转换。
类似于下面的code。
if (!strncasecmp("/storage/emulated/0", mImageUrl, 19)) {
using std::string;
string imagePath = string(mImageUrl);
imagePath = imagePath.substr(19);
imagePath = string("/storage/emulated/legacy") + imagePath;
strncpy(mImageUrl, imagePath.c_str(), imagePath.length() + 1);
ALOGD("setDataSource image path replaced %s", mImageUrl);
}
原理学习主要来自下面这篇blog,也转载于此。
http://www.cloudchou.com/android/post-689.html
emultaed技术,即多用户挂载技术,是Android 4.2引入的一项新的Sd卡挂载技术,不同的用户看到的Sd卡挂载目录不一样。
比如使用adb shell查看sd卡目录可能得到的Sd卡目录是/storage/emulated/legacy,它实际上是一个链接目录:
|
|
这样/mnt/shell/emulated/0是sd卡的实际目录,但是在应用程序里即使用Root权限查看/storage/emulated/legacy目录,会发现该目录根本不存在。
应用程序通过系统api获取Sd卡路径时得到的Sd卡路径是/storage/emulated/0,同样我们使用adb shell去查看该目录时也会发现该目录不存在。
linux内核从2.6.x引入了共享挂载,从属挂载,私有挂载,绑定挂载,挂载传播技术。
Android 4.2就是利用了共享挂载和从属挂载来隔离挂载的命名空间,不同用户的挂载对别的进程或者用户不可见。
关于挂载技术的资料:
https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
命名空间相关资料:
http://book.51cto.com/art/201005/200881.htm
首先我们看一下init.rc里storage,mnt目录的创建:
|
export EXTERNAL_STORAGE /storage/emulated/legacy
export EMULATED_STORAGE_SOURCE /mnt/shell/emulated
export EMULATED_STORAGE_TARGET /storage/emulated
# Support legacy paths
symlink /storage/emulated/legacy /sdcard
symlink /storage/emulated/legacy /mnt/sdcard
symlink /storage/emulated/legacy /storage/sdcard0
symlink /mnt/shell/emulated/0 /storage/emulated/legacy
mkdir /storage/external_storage 0666 system system
mount tmpfs tmpfs /storage/external_storage rec mode=0775,gid=1000
# Backward compatibility
symlink /storage/external_storage /mnt/usb
可以看到有如下链接关系:
|
|
通过mount命令可以看到:
|
|
我们可以看到真实的挂载目录其实是/mnt/shell/emulated,不过使用的是fuse设备。
由此我们可以理解为什么使用 adb shell ls -l /storage/emulated 看到的是:
|
|
再看一下在应用程序里使用SuperUser执行命令 ls -l /storage/emulated/得到的结果:
|
|
为什么会多了一个0目录呢? 为什么legacy目录成为真实目录, 而不是软链接了。
知道Android应用启动过程的读者一定知道应用启动时是通过zygote使用fork孵化出来的进程,
而zygote启动时会执行dvmStartup -> initZygote (dalvik/vm/Init.cpp)
static bool initZygote()
{
/* zygote goes into its own process group */
setpgid(0,0);
// See storage config details at http://source.android.com/tech/storage/
// Create private mount namespace shared by all children
//先前有提到unshare,此处调用是为了创建新的命名空间,
//CLONE_NEWNS表示mount类型的命名空间,
//这样子进程和父进程的mount命名空间不再是同一个了
if (unshare(CLONE_NEWNS) == -1) {
SLOGE("Failed to unshare(): %s", strerror(errno));
return -1;
}
// Mark ANDROID_STORAGE (e.g., /storage) as being a slave so that changes
// from default namespace only flow into our children. Notes:
// 1. ANDROID_STORAGE must already serve as a mountpoint, e.g., for a tmpfs
// volume, as only mountpoints may be marked and rootfs cannot be bind
// mounted.
// 2. EMULATED_STORAGE_TARGET (e.g., /storage/emulated), if used, must be
// path-prefixed by ANDROID_STORAGE.
// 3. Multi-user sandbox mounts must be made under ANDROID_STORAGE to
// remain hidden from other users.
// 4. Dalvik-based apps may mount anywhere, except under ANDROID_STORAGE,
// to provide a system-wide shared mount between apps and users.
//ANDROID_STORAGE 通常是指 /storage目录
const char* storage_base = getenv("ANDROID_STORAGE");
if (storage_base != NULL) {
//修改/storage的挂载属性为从属挂载,MS_SLAVE是指从属挂载,MS_REC表示递归修改
if (mount(NULL, storage_base, NULL, (MS_SLAVE | MS_REC), NULL) == 0) {
// ANDROID_STORAGE successfully marked as slave, leave the rest of the
// filesystem hierarchy marked as shared.
goto mounted_slave;
} else {
// Warn only, init.rc is likely missing a tmpfs mount for ANDROID_STORAGE.
SLOGW("Failed to mount %s as MS_SLAVE: %s", storage_base, strerror(errno));
}
} else {
SLOGW("ANDROID_STORAGE environment variable undefined");
}
// Fallback: Mark rootfs as being a slave so that changes from default
// namespace only flow into our children. All mounts under "/" will be
// hidden from other apps and users.
if (mount("rootfs", "/", NULL, (MS_SLAVE | MS_REC), NULL) == -1) {
SLOGE("Failed to mount() rootfs as MS_SLAVE: %s", strerror(errno));
return -1;
}
mounted_slave:
// Create a staging tmpfs that is shared by our children; they will
// bind mount storage into their respective private namespaces, which
// are isolated from each other.
//EMULATED_STORAGE_TARGET环境变量一般指/storage/emulated
const char* target_base = getenv("EMULATED_STORAGE_TARGET");
if (target_base != NULL) {
//挂载tmpfs放到/storage/emulated目录,
//此时先前建立的软链接/storage/emulated/legacy不再可见,
//只有目录/storage/emulated了
if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
"uid=0,gid=1028,mode=0050") == -1) {
SLOGE("Failed to mount tmpfs to %s: %s", target_base, strerror(errno));
return -1;
}
}
return true;
}
我们再看应用启动时如何挂载/storage/emulated/0目录的,我们知道应用启动时是通过Zygote孵化,会调用到Zygote.forkAndSpecialize,再看一下它接下来的调用流程:
Zygote.forkAndSpecialize -> Dalvik_dalvik_system_Zygote_forkAndSpecialize(dalvik_system_Zygote.cpp) -> forkAndSpecializeCommon(dalvik_system_Zygote.cpp)->mountEmulatedStorage
由此可知道是在mountEmulatedStorage进行模拟挂载的,我们看一下这个函数的关键代码:
/*
* Create a private mount namespace and bind mount appropriate emulated
* storage for the given user.
*/
static int mountEmulatedStorage(uid_t uid, u4 mountMode) {
// See storage config details at http://source.android.com/tech/storage/
//此处获取multiuser_get_user_id其实是用uid/100000得到userid,也就是说一个用户至多只有100000个用户
userid_t userid = multiuser_get_user_id(uid);
// Create a second private mount namespace for our process
//和initZygote时一样建立自己的mount命名空间
if (unshare(CLONE_NEWNS) == -1) {
ALOGE("Failed to unshare(): %s", strerror(errno));
return -1;
}
// Create bind mounts to expose external storage
//多用户环境下mountMode一般都是MOUNT_EXTERNAL_MULTIUSER或者MOUNT_EXTERNAL_MULTIUSER_ALL
if (mountMode == MOUNT_EXTERNAL_MULTIUSER
|| mountMode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
// These paths must already be created by init.rc
//先前我们看到的init.rc里也指出了环境变量EMULATED_STORAGE_SOURCE,
//EMULATED_STORAGE_TARGET,EMULATED_STORAGE_TARGET的值
//一般情况下这些环境变量的值:
//EMULATED_STORAGE_SOURCE /mnt/shell/emulated
//EMULATED_STORAGE_TARGET /storage/emulated
//EXTERNAL_STORAGE /storage/emulated/legacy
const char* source = getenv("EMULATED_STORAGE_SOURCE");
const char* target = getenv("EMULATED_STORAGE_TARGET");
const char* legacy = getenv("EXTERNAL_STORAGE");
if (source == NULL || target == NULL || legacy == NULL) {
ALOGE("Storage environment undefined; unable to provide external storage");
return -1;
}
// Prepare source paths
char source_user[PATH_MAX];
char source_obb[PATH_MAX];
char target_user[PATH_MAX];
// /mnt/shell/emulated/0
snprintf(source_user, PATH_MAX, "%s/%d", source, userid);
// /mnt/shell/emulated/obb
snprintf(source_obb, PATH_MAX, "%s/obb", source);
// /storage/emulated/0
snprintf(target_user, PATH_MAX, "%s/%d", target, userid);
//建立各种目录,若目录已存在则不用重复创建
if (fs_prepare_dir(source_user, 0000, 0, 0) == -1
|| fs_prepare_dir(source_obb, 0000, 0, 0) == -1
|| fs_prepare_dir(target_user, 0000, 0, 0) == -1) {
return -1;
}
// Unfortunately bind mounts from outside ANDROID_STORAGE retain the
// recursive-shared property (kernel bug?). This means any additional bind
// mounts (e.g., /storage/emulated/0/Android/obb) will also appear, shared
// in all namespaces, at their respective source paths (e.g.,
// /mnt/shell/emulated/0/Android/obb), leading to hundreds of
// /proc/mounts-visible bind mounts. As a workaround, mark
// EMULATED_STORAGE_SOURCE (e.g., /mnt/shell/emulated) also a slave so that
// subsequent bind mounts are confined to this namespace. Note,
// EMULATED_STORAGE_SOURCE must already serve as a mountpoint, which it
// should for the "sdcard" fuse volume.
//将/mnt/shell/emulated变成从属挂载
if (mount(NULL, source, NULL, (MS_SLAVE | MS_REC), NULL) == -1) {
SLOGW("Failed to mount %s as MS_SLAVE: %s", source, strerror(errno));
// Fallback: Mark rootfs as slave. All mounts under "/" will be hidden
// from other apps and users. This shouldn't happen unless the sdcard
// service is broken.
if (mount("rootfs", "/", NULL, (MS_SLAVE | MS_REC), NULL) == -1) {
SLOGE("Failed to mount rootfs as MS_SLAVE: %s", strerror(errno));
return -1;
}
}
if (mountMode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
// Mount entire external storage tree for all users
//将/mnt/shell/emulated 绑定到 /storage/emulated
if (mount(source, target, NULL, MS_BIND, NULL) == -1) {
ALOGE("Failed to mount %s to %s: %s", source, target, strerror(errno));
return -1;
}
} else {
// Only mount user-specific external storage
//将/mnt/shell/emulated/0 绑定到 /storage/emulated/0,
//这样就有了/storage/emulated/0,因为/storage是从属挂载方式,
//故此adb shell看不到这个0目录
if (mount(source_user, target_user, NULL, MS_BIND, NULL) == -1) {
ALOGE("Failed to mount %s to %s: %s", source_user, target_user, strerror(errno));
return -1;
}
}
// Now that user is mounted, prepare and mount OBB storage
// into place for current user
char target_android[PATH_MAX];
char target_obb[PATH_MAX];
// /storage/emulated/0/Android
snprintf(target_android, PATH_MAX, "%s/%d/Android", target, userid);
// /storage/emulated/0/Android/obb
snprintf(target_obb, PATH_MAX, "%s/%d/Android/obb", target, userid);
if (fs_prepare_dir(target_android, 0000, 0, 0) == -1
|| fs_prepare_dir(target_obb, 0000, 0, 0) == -1
|| fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
return -1;
}
if (mount(source_obb, target_obb, NULL, MS_BIND, NULL) == -1) {
ALOGE("Failed to mount %s to %s: %s", source_obb, target_obb, strerror(errno));
return -1;
}
// Finally, mount user-specific path into place for legacy users
//将 /storage/emulated/legacy 绑定到 /storage/emulated/0,
//故此看到 /storage/emulated/legacy是一个真实目录,
//不再是软链接,并且该目录下的内容和sd卡下的内容一致
if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) == -1) {
ALOGE("Failed to mount %s to %s: %s", target_user, legacy, strerror(errno));
return -1;
}
} else {
ALOGE("Mount mode %d unsupported", mountMode);
return -1;
}
return 0;
}
由上可以解释: 为什么会多了一个0目录呢? 为什么legacy目录成为真实目录, 而不是软链接了
/storage是从属挂载,应用启动时会在/storage/emulated建立0目录和legacy目录,但是因为是从属挂载,故此通过adb shell看不到该目录
应用启动时采取绑定挂载的方式将/mnt/shell/emulated/0挂载到了/storage/emulated/0,还采取了绑定挂载的方式将/storage/emulated/0挂载到了/storage/emulated/legacy。