Android 4.4就实现了多用户功能,但是一直没有正式开发,直到Android 7.0添加了基于文件的加密能力,每个用户可以使用自己的加密空间,这才完全开放,今天我们就来分析下Android的多用户目录管理。在Android存储系统-MountService 和vold 对外置存储的管理(1) 一文中我们看到Android的MountService启动后会发送user_added 和 user_started命令。 来回顾一下:
frameworks/base/services/core/java/com/android/server/MountService.java
private void resetIfReadyAndConnectedLocked() {
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ ", mDaemonConnected=" + mDaemonConnected);
if (mSystemReady && mDaemonConnected) {
killMediaProvider();
mDisks.clear();
mVolumes.clear();
addInternalVolume();
try {
mConnector.execute("volume", "reset");
// Tell vold about all existing and started users
final UserManager um = mContext.getSystemService(UserManager.class);
final List<UserInfo> users = um.getUsers();
for (UserInfo user : users) {
mConnector.execute("volume", "user_added", user.id, user.serialNumber);
}
for (int userId : mStartedUsers) {
mConnector.execute("volume", "user_started", userId);
}
} catch (NativeDaemonConnectorException e) {
Slog.w(TAG, "Failed to reset vold", e);
}
}
}
resetIfReadyAndConnectedLocked函数在systemReady时被调用,首先发送volume reset命令触发磁盘的重新挂载。之后发送user_added命令,来添加用户, 最后发送user_started命令来启动用户。 磁盘挂载过程我们已经在Android存储系统-MountService 和vold 对外置存储的管理(1)中进行了分析,这里只关注多用户相关的命令。
system/vold/CommandListener.cpp
int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
......
} else if (cmd == "user_added" && argc > 3) {
// user_added [user] [serial]
return sendGenericOkFail(cli, vm->onUserAdded(atoi(argv[2]), atoi(argv[3])));
}
......
}
对于user_added命令的处理,直接调用vm->onUserAdded(atoi(argv[2]), atoi(argv[3]))来完成。
system/vold/VolumeManager.cpp
int VolumeManager::onUserAdded(userid_t userId, int userSerialNumber) {
mAddedUsers[userId] = userSerialNumber;
return 0;
}
addUser的处理很简单,只是将user的序列号按照userId保存起来。 再来看下user_started命令的处理:
system/vold/CommandListener.cpp
int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
......
} else if (cmd == "user_started" && argc > 2) {
// user_started [user]
return sendGenericOkFail(cli, vm->onUserStarted(atoi(argv[2])));
}
......
}
同样调用vm->onUserStarted(atoi(argv[2])来处理。
system/vold/VolumeManager.cpp
static const char* kUserMountPath = "/mnt/user";
int VolumeManager::onUserStarted(userid_t userId) {
// Note that sometimes the system will spin up processes from Zygote
// before actually starting the user, so we're okay if Zygote
// already created this directory.
std::string path(StringPrintf("%s/%d", kUserMountPath, userId));
fs_prepare_dir(path.c_str(), 0755, AID_ROOT, AID_ROOT);
mStartedUsers.insert(userId);
if (mPrimary) {
linkPrimary(userId);
}
return 0;
}
onUserStarted函数首先创建/mnt/user/${userid} 目录,然后将userId插入到以启动的user集合里面。最后如果已经确定主存储,调用linkPrimary(userId) 来链接主存储上user的目录空间。
system/vold/VolumeManager.cpp
421 int VolumeManager::linkPrimary(userid_t userId) {
422 std::string source(mPrimary->getPath());
423 if (mPrimary->getType() == android::vold::VolumeBase::Type::kEmulated) {
424 source = StringPrintf("%s/%d", source.c_str(), userId);
425 fs_prepare_dir(source.c_str(), 0755, AID_ROOT, AID_ROOT);
426 }
427
428 std::string target(StringPrintf("/mnt/user/%d/primary", userId));
429 if (TEMP_FAILURE_RETRY(unlink(target.c_str()))) {
430 if (errno != ENOENT) {
431 SLOGW("Failed to unlink %s: %s", target.c_str(), strerror(errno));
432 }
433 }
434 LOG(DEBUG) << "Linking " << source << " to " << target;
435 if (TEMP_FAILURE_RETRY(symlink(source.c_str(), target.c_str()))) {
436 SLOGW("Failed to link %s to %s: %s", source.c_str(), target.c_str(),
437 strerror(errno));
438 return -errno;
439 }
440 return 0;
441 }
如果主存储是一个PublicVolume, 则主存储只有挂载它的用户可见,不用区分多用户,否则在主存储的目录下创建 ${userid}目录,然后将 /mnt/user/${userid}/primary 用软连接指向 主存储下的${userid}目录,给该用户在主存储上划出一个专用目录。 所以在我们在Android存储系统-MountService 和vold 对外置存储的管理(1) 一文中看到Pixel的多用户目录如下
/mnt/user/0/primary -> /storage/emulated/0