背景来源
文件系统的多用户支持,严格说来和FUSE关系不大,sdcardfs文件系统也支持多用户机制,Android系统中的多用户就是把不同用户对应的目录分别挂载,独立操作。user 0是缺省用户,单用户就只有一个user 0, 多用户则还有一个或者多个用户,第二个用户缺省从10开始,每增加一个就用户则在此基础上加一,如第三个用户就是user 11,以此类推。 在底层文件系统上user 0 用户的根目录是/data/media/0,而user 10用户对应的是/data/media/10,而挂载过程则是是把/data/media/0和/data/media/10独立挂载。所以对于文件系统而言是没有区别的,单用户和多用户的主要的差异就是在开机阶段的文件系统的挂载过程。
在Android的原生系统中支持多个用户的切换,每个用户可以看到不同的视图,类似于Windows的多用户机制。而在国内的手机厂家(小米,华为等)中一般都是利用多用户机制来实现应用双开,所谓应用双开就是可以同时安装两个微信登陆不同的账号。在实际工作中遇到从Android 10 sdcardfs升级到Android 11 Fuse文件系统,应用双开功能出现多个问题,为解决这些问题,对基于FUSE的多用户机制进行了分析。
多用户的创建命令
应用双开实际上就是Android多用户的应用场景,创建多用户的命令
adb shell pm create-user [--profileOf USER_ID] [--managed] USER_NAME
多用户的创建流程
PackageManagerShellCommand接收到create-user命令,会通过UserManager提供的接口,通过Binder调用到UserManager里面,通过几个函数调用,最终多用户调用的主要流程是在UserManagerService.createUserInternalUncheckedNoTracing实现的
less
private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name,
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t) throws UserManager.CheckedUserOperationException {
。。。。
try {
synchronized (mPackagesLock) {
。。。。
// 获取UserID,应用双开用户名doppelganger,id 10,其他新建user id从11开始
//Smt: @Feature [114808] app doppelganger {@
//userId = getNextAvailableId();
if ((flags & UserInfoSmtEx.FLAG_DOPPELGANGER) == UserInfoSmtEx.FLAG_DOPPELGANGER) {
userId = UserHandleSmtEx.USER_DOPPELGANGER;
} else {
userId = getNextAvailableId();
}
//@}
// 创建 "/data/system/users/${userId}"目录
Environment.getUserSystemDirectory(userId).mkdirs();
// 构造UserInfo和UserData信息
synchronized (mUsersLock) {
// Inherit ephemeral flag from parent.
if (parent != null && parent.info.isEphemeral()) {
flags |= UserInfo.FLAG_EPHEMERAL;
}
// Always clear EPHEMERAL for pre-created users, otherwise the storage key
// won't be persisted. The flag will be re-added (if needed) when the
// pre-created user is "converted" to a normal user.
if (preCreate) {
flags &= ~UserInfo.FLAG_EPHEMERAL;
}
userInfo = new UserInfo(userId, name, null, flags, userType);
userInfo.serialNumber = mNextSerialNumber++;
userInfo.creationTime = getCreationTime();
userInfo.partial = true; //这个是用来保证创建过程断电,重启后继续
userInfo.preCreated = preCreate;
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
if (userTypeDetails.hasBadge() && parentId != UserHandle.USER_NULL) {
userInfo.profileBadge = getFreeProfileBadgeLU(parentId, userType);
}
userData = new UserData();
userData.info = userInfo;
mUsers.put(userId, userData);
}
// 将上述UserData信息保存到 "/data/system/users/${userId}.xml"
writeUserLP(userData);
// 将新创建的userId保存到 "/data/system/users/userlist.xml"
writeUserListLP();
。。。。
// 通过StorageManager,最终通过vold对新用户初始化的密钥数据,完成FBE加密
t.traceBegin("createUserKey");
final StorageManager storage = mContext.getSystemService(StorageManager.class);
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
t.traceEnd();
// 通过vold创建以下目录,并赋予相关rwx权限:
// "/data/system/users/${userId}" : 0700
// "/data/misc/users/${userId}" : 0750
t.traceBegin("prepareUserData");
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
t.traceEnd();
final Set<String> userTypeInstallablePackages =
mSystemPackageInstaller.getInstallablePackagesForUserType(userType);
// 为已安装应用创建"/data/system/user/${userId}/${packageName}"目录
// 还处理权限等创建新用户相关的主要操作
t.traceBegin("PM.createNewUser");
mPm.createNewUser(userId, userTypeInstallablePackages, disallowedPackages);
t.traceEnd();
// 到此为止创建用户的主要工作完成,把partial设置为False
userInfo.partial = false;
// 更新用户信息
synchronized (mPackagesLock) {
writeUserLP(userData);
}
// 更新所有缓存的用户
updateUserIds();
。。。。
// 为新创建的用户赋予默认权限
t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
t.traceEnd();
if (preCreate) {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
// intialize it.
// TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
// callback on SystemService instead.
Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString());
final IActivityManager am = ActivityManager.getService();
try {
// 在后台启动新用户,应用双开属于这个场景
am.startUserInBackground(userId);
} catch (RemoteException e) {
Slog.w(LOG_TAG, "could not start pre-created user " + userId, e);
}
} else {
// 向所有用户发送 "ACTION_USER_ADDED" 广播
dispatchUserAdded(userInfo);
}
。。。。
return userInfo;
}
创建用户的序列图
多用户的启动命令
adb shell am start-user USER_ID
多用户的启动流程
处理start-user的入口在ActivityManagerShellCommand.runStartUser里面
markdown
int runStartUser(PrintWriter pw) throws RemoteException {
。。。。
// 调用
boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
。。。。
}
// 最后主要的逻辑在这个函数里面
private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long ident = Binder.clearCallingIdentity();
try {
t.traceBegin("getStartedUserState");
final int oldUserId = getCurrentUserId();
if (oldUserId == userId) {
final UserState state = getStartedUserState(userId);
if (state == null) {
Slog.wtf(TAG, "Current user has no UserState");
// continue starting.
} else {
if (userId == UserHandle.USER_SYSTEM && state.state == STATE_BOOTING) {
// system user start explicitly requested. should continue starting as it
// is not in running state.
} else {
if (state.state == STATE_RUNNING_UNLOCKED) {
// We'll skip all later code, so we must tell listener it's already
// unlocked.
notifyFinished(userId, unlockListener);
}
t.traceEnd(); //getStartedUserState
return true;
}
}
}
t.traceEnd(); //getStartedUserState
if (foreground) {
t.traceBegin("clearAllLockedTasks");
mInjector.clearAllLockedTasks("startUser");
t.traceEnd();
}
t.traceBegin("getUserInfo");
final UserInfo userInfo = getUserInfo(userId);
t.traceEnd();
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
}
if (foreground && userInfo.isManagedProfile()) {
Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
return false;
}
if (foreground && userInfo.preCreated) {
Slog.w(TAG, "Cannot start pre-created user #" + userId + " as foreground");
return false;
}
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
R.anim.screen_user_exit, R.anim.screen_user_enter);
t.traceEnd();
}
boolean needStart = false;
boolean updateUmState = false;
UserState uss;
// If the user we are switching to is not currently started, then
// we need to start it now.
t.traceBegin("updateStartedUserArrayStarting");
synchronized (mLock) {
uss = mStartedUsers.get(userId);
if (uss == null) {
uss = new UserState(UserHandle.of(userId));
uss.mUnlockProgress.addListener(new UserProgressListener());
mStartedUsers.put(userId, uss);
updateStartedUserArrayLU();
needStart = true;
updateUmState = true;
} else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
Slog.i(TAG, "User #" + userId
+ " is shutting down - will start after full stop");
mHandler.post(() -> startUser(userId, foreground, unlockListener));
t.traceEnd(); // updateStartedUserArrayStarting
return true;
}
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
}
if (unlockListener != null) {
uss.mUnlockProgress.addListener(unlockListener);
}
t.traceEnd(); // updateStartedUserArrayStarting
if (updateUmState) {
t.traceBegin("setUserState");
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
t.traceEnd();
}
t.traceBegin("updateConfigurationAndProfileIds");
if (foreground) {
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
boolean userSwitchUiEnabled;
synchronized (mLock) {
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
updateCurrentProfileIds();
mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
mInjector.getWindowManager().lockNow(null);
}
} else {
final Integer currentUserIdInt = mCurrentUserId;
updateCurrentProfileIds();
mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
synchronized (mLock) {
mUserLru.remove(currentUserIdInt);
mUserLru.add(currentUserIdInt);
}
}
t.traceEnd();
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.state == UserState.STATE_STOPPING) {
t.traceBegin("updateStateStopping");
// If we are stopping, we haven't sent ACTION_SHUTDOWN,
// so we can just fairly silently bring the user back from
// the almost-dead.
uss.setState(uss.lastState);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
synchronized (mLock) {
updateStartedUserArrayLU();
}
needStart = true;
t.traceEnd();
} else if (uss.state == UserState.STATE_SHUTDOWN) {
t.traceBegin("updateStateShutdown");
// This means ACTION_SHUTDOWN has been sent, so we will
// need to treat this as a new boot of the user.
uss.setState(UserState.STATE_BOOTING);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
synchronized (mLock) {
updateStartedUserArrayLU();
}
needStart = true;
t.traceEnd();
}
if (uss.state == UserState.STATE_BOOTING) {
t.traceBegin("updateStateBooting");
// Give user manager a chance to propagate user restrictions
// to other services and prepare app storage
mInjector.getUserManager().onBeforeStartUser(userId);
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0));
t.traceEnd();
}
t.traceBegin("sendMessages");
if (foreground) {
mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
oldUserId, userId, uss));
mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
}
if (userInfo.preCreated) {
needStart = false;
}
if (needStart) {
// Send USER_STARTED broadcast
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
}
t.traceEnd();
if (foreground) {
t.traceBegin("moveUserToForeground");
moveUserToForeground(uss, oldUserId, userId);
t.traceEnd();
} else {
t.traceBegin("finishUserBoot");
finishUserBoot(uss);
t.traceEnd();
}
if (needStart) {
t.traceBegin("sendRestartBroadcast");
Intent intent = new Intent(Intent.ACTION_USER_STARTING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mInjector.broadcastIntent(intent,
null, new IIntentReceiver.Stub() {
@Override
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered,
boolean sticky,
int sendingUser) throws RemoteException {
}
}, 0, null, null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
null, true, false, MY_<span data-word-id="51586842" class="abbreviate-word">PID</span>, SYSTEM_<span data-word-id="304" class="abbreviate-word">UID</span>, callingUid, callingPid,
UserHandle.USER_ALL);
t.traceEnd();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
启动用户的序列图
应用双开挂载失败问题分析
问题现象
手机系统在开启应用双开功能后,高概率出现各种应用崩溃现象,经分析发现出现问题的时候user 0和 user 10的内卡目录都没有挂载成功,因此出现各种问题。经过仔细分析发现是多用户代码挂载流程的问题,具体分析如下。
Google现有流程
Google的多用户是有界面操作的,是在手机正常启动后,进行手动切换的。因此即使在创建了多用户的情况下,挂载也是先完成user 0的start,然后才会进行user 10的start操作。
应用双开现有流程
应用双开后再次启动,user 0和user 10会同时start,整个操作是一个并发过程,整个启动过程逻辑复杂。
看出问题的Log里面,在user 0没有启动完成的时候,又开始了user 10的启动,出现2秒内,连续reset vold的操作,相互影响,导致user 0和user 10都无法正常起来。总之,这时候整个系统是一个混乱的状态。
css
06-16 11:41:45.713684 1570 2068 D StorageManagerService: Thinking about init, mBootCompleted=true, mDaemonConnected=true
06-16 11:41:45.713719 1570 2068 D StorageManagerService: Thinking about reset, mBootCompleted=true, mDaemonConnected=true
06-16 11:41:45.713737 1570 3097 V WindowManager: Orientation start waiting for <span data-word-id="809" class="abbreviate-word">draw</span>, mDrawState=DRAW_PENDING in Window{63760fc u0 com.android.<span data-word-id="1063" class="abbreviate-word">settings</span>/com.android.settings.FallbackHome}, surfaceController Surface(name=com.android.settings/com.android.settings.FallbackHome)/@0x22a1730
06-16 11:41:45.713773 1570 3097 V WindowManager: Orientation start waiting for draw, mDrawState=DRAW_PENDING in Window{88be6d6 u0 com.android.systemui.ImageWallpaper}, surfaceController Surface(name=com.android.systemui.ImageWallpaper)/@0xf1b2ce5
06-16 11:41:45.713956 1570 2068 I StorageSessionController: Started resetting external storage service...
06-16 11:41:45.714006 1570 2068 I StorageSessionController: Finished resetting external storage service
06-16 11:41:45.714047 1570 2068 I StorageManagerService: Resetting vold...
06-16 11:41:45.714363 1570 2068 I StorageManagerService: Reset vold
06-16 11:41:45.714436 652 652 I vold : onUserAdded: 0
06-16 11:41:45.714522 652 652 I vold : onUserAdded: 10
06-16 11:41:45.714685 598 598 I servicemanager: Found android.hardware.power.IPower/default in device VINTF manifest.
06-16 11:41:45.714928 972 1592 I PowerAdvisor: Loaded AIDL Power HAL service
ini
06-16 11:41:57.131360 1570 1644 W NotificationHistory: Attempted to remove conversation for locked/gone/disabled user 10
06-16 11:41:57.174185 1570 2068 D StorageManagerService: Thinking about reset, mBootCompleted=true, mDaemonConnected=true
06-16 11:41:57.174295 1570 2068 I StorageSessionController: Started resetting external storage service...
06-16 11:41:57.174391 1570 2068 I StorageUserConnection: Closing connection for user 10
06-16 11:41:57.174423 1570 2068 I StorageSessionController: Finished resetting external storage service
06-16 11:41:57.174460 1570 2068 I StorageManagerService: Resetting vold...
06-16 11:41:57.175160 1570 2068 I StorageManagerService: Reset vold
06-16 11:41:57.175237 652 662 I vold : onUserAdded: 0
06-16 11:41:57.175330 652 662 I vold : onUserAdded: 10
06-16 11:41:57.175410 652 662 I vold : onUserStarted: 10
06-16 11:41:57.175639 1570 3097 V StorageManagerService: Found primary storage at VolumeInfo{emulated;10}:
06-16 11:41:57.175639 1570 3097 V StorageManagerService: type=EMULATED diskId=null partGuid= mountFlags=0 mountUserId=10
06-16 11:41:57.175639 1570 3097 V StorageManagerService: state=UNMOUNTED
06-16 11:41:57.175639 1570 3097 V StorageManagerService: fsType=null fsUuid=null fsLabel=null
06-16 11:41:57.175639 1570 3097 V StorageManagerService: path=null internalPath=null
06-16 11:41:57.176254 1570 2068 I StorageManagerService: Mounting volume VolumeInfo{emulated;10}:
06-16 11:41:57.176254 1570 2068 I StorageManagerService: type=EMULATED diskId=null partGuid= mountFlags=PRIMARY|VISIBLE
06-16 11:41:57.176254 1570 2068 I StorageManagerService: mountUserId=10 state=UNMOUNTED
06-16 11:41:57.176254 1570 2068 I StorageManagerService: fsType=null fsUuid=null fsLabel=null
06-16 11:41:57.176254 1570 2068 I StorageManagerService: path=null internalPath=null
06-16 11:41:57.176571 652 662 I vold : Mounting emulated fuse volume
06-16 11:41:57.177047 4599 4618 D ActivityThread: create app==>app on ext display, displayId: -1
06-16 11:41:57.183858 652 662 I vold : Bind mounting /mnt/runtime/full/emulated to /mnt/pass_through/10/emulated
应用双开流程改进流程
由于user 0是整个系统的缺省基础用户,必须启动的。因此,应用双开流程按照如下方式修改,修改完成后问题解决。
- user 10不能和user 0一起start(unlock),user 10要等user 0彻底挂载完毕后才可以起。
- User 0如果不成功挂载,不能进行user 10的start(unlock)