深入分析Android 11 FUSE文件系统(六) ---多用户

背景来源

文件系统的多用户支持,严格说来和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是整个系统的缺省基础用户,必须启动的。因此,应用双开流程按照如下方式修改,修改完成后问题解决。

  1. user 10不能和user 0一起start(unlock),user 10要等user 0彻底挂载完毕后才可以起。
  2. User 0如果不成功挂载,不能进行user 10的start(unlock)

索引

深入分析Android 11 FUSE文件系统

参考资料

深入理解Android系统多用户_ulangch的博客-CSDN博客_android多用户

相关推荐
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k5 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小106 小时前
JavaWeb项目-----博客系统
android
风和先行6 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.7 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰8 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶8 小时前
Android——网络请求
android
干一行,爱一行8 小时前
android camera data -> surface 显示
android
断墨先生8 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员10 小时前
PHP常量
android·ide·android studio