前言

        在Android存储系统-使用fuse来管理外置存储 一文中我们介绍了Android fuse 来实现灵活的权限管理,我们知道获取不同的外置存储权限使用的外置存储目录是不同的, 获取外置存储读权限的应用程序看到的fuse目录为/mnt/runtime/read/${label}目录,获取了外置存储写权限的应用程序使用的是fuse的/mnt/runtime/write/${label}目录,而没有获取任何外置存储权限的应用看到的fuse目录为/mnt/runtime/default/${label}, 这一点是如何做到的呢? 在权限组管理方面,是如何将应用程序添加到AID_EVERYBODY组的呢? 外置存储权限又是如何动态授权和撤销的呢? 今天我们就来说明这个问题。

Android 设计

        Android在启动应用程序进程的时候,会通过PackageManagerService 来查询应用所属的组以及是否获取了应用外置存储权限。这些信息都会当做参数传递给zygote服务, zygote服务 fork应用进程后,根据参数中的组信息来设置应用进程所属于的组。再根据不同的外置存储权限,利用Linux的mount bind技术,将对应的fuse目录挂载到外置存储目录,这样以后读写外置存储目录,实际上写入到了fuse目录,这样就利用到了fuse权限管理能力。 另外Android还使用的命名空间技术, 每个应用使用主空间的一个副本,当应用程序外置存储权限改变的时候,只需要重新挂载该命名空间即可, 好了不多说,直接看代码。

代码分析

        Andrioid 启动应用的时候会在ActivityManagerService中调用startProcessLocked函数,代码如下:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
        ......
        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
            if (!app.isolated) {
                int[] permGids = null;
                try {
                    checkTime(startTime, "startProcess: getting gids from package manager");
                    final IPackageManager pm = AppGlobals.getPackageManager();
                    permGids = pm.getPackageGids(app.info.packageName,
                            MATCH_DEBUG_TRIAGED_MISSING, app.userId);
                    MountServiceInternal mountServiceInternal = LocalServices.getService(
                            MountServiceInternal.class);
                    mountExternal = mountServiceInternal.getExternalStorageMountMode(uid,
                            app.info.packageName);
                } catch (RemoteException e) {
                    throw e.rethrowAsRuntimeException();
                }
    ......
   Process.ProcessStartResult startResult = Process.start(entryPoint,
               app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);
......

        应用进程启动之前要准备一系列参数,这些参数最终会传递给zygote进程,用于fork应用进程。这里面有关外置存储权限管理的两个参数就是permGids和mountExternal。我们先来看permGids如何获取。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@Override
    public int[] getPackageGids(String packageName, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        flags = updateFlagsForPackage(flags, userId, packageName);
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "getPackageGids");

        // reader
        synchronized (mPackages) {
            final PackageParser.Package p = mPackages.get(packageName);
            if (p != null && p.isMatch(flags)) {
                PackageSetting ps = (PackageSetting) p.mExtras;
                return ps.getPermissionsState().computeGids(userId);
            }
            if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0) {
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps != null && ps.isMatch(flags)) {
                    return ps.getPermissionsState().computeGids(userId);
                }
            }
        }

        return null;
    }

        getPackageGids函数通过权限管理计算gid,也就是ps.getPermissionsState().computeGids(userId)这个函数。

frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java

427     /**
428      * Compute the Linux gids for a given device user from the permissions
429      * granted to this user. Note that these are computed to avoid additional
430      * state as they are rarely accessed.
431      *
432      * @param userId The device user id.
433      * @return The gids for the device user.
434      */
435     public int[] computeGids(int userId) {
436         enforceValidUserId(userId);
437 
438         int[] gids = mGlobalGids;
439 
440         if (mPermissions != null) {
441             final int permissionCount = mPermissions.size();
442             for (int i = 0; i < permissionCount; i++) {
443                 String permission = mPermissions.keyAt(i);
444                 if (!hasPermission(permission, userId)) {
445                     continue;
446                 }
447                 PermissionData permissionData = mPermissions.valueAt(i);
448                 final int[] permGids = permissionData.computeGids(userId);
449                 if (permGids != NO_GIDS) {
450                     gids = appendInts(gids, permGids);
451                 }
452             }
453         }
454
455         return gids;
456     }

        447-451行如果应用获取了权限,就看下该权限是否关联了gid,如果关联了gid就放在返回结果的数组里面。最后返回应用获取的所有权限关联的gid,那么哪些权限会关联gid呢,在PackageManagerService构造函数中会进行初始化。注意这里mGroupGids是应用默认的gid,也在PackageManagerService构造函数中设置。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
       ......
       SystemConfig systemConfig = SystemConfig.getInstance();
        mGlobalGids = systemConfig.getGlobalGids();
       ......
       // Propagate permission configuration in to package manager.
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions();
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                if (bp == null) {
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                    bp.setGids(perm.gids, perm.perUser);
                }
            }
         ......
   }

        这里可以看出,gid是从systemConfig.getPermissions()取出来的, systemConfig是SystemConfig 类的实例。

frameworks/base/core/java/com/android/server/SystemConfig.java

public ArrayMap<String, PermissionEntry> getPermissions() {
        return mPermissions;
    }

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        // Allow ODM to customize system configs around libs, features and apps
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
        // Only allow OEM to customize features
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    }

        SystemConfig初始化过程会读取一些列文件,来获取权限和gid的关系,举个例子,在我的手机上
/system/etc/permissions/platform.xml文件内容如下。

<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
    <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
    <assign-permission name="android.permission.INTERNET" uid="media" />

    <assign-permission name="android.permission.INTERNET" uid="shell" />
    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
        <group gid="media_rw" />
    </permission>

<split-permission name="android.permission.WRITE_EXTERNAL_STORAGE">
        <new-permission name="android.permission.READ_EXTERNAL_STORAGE" />
    </split-permission>
    <split-permission name="android.permission.WRITE_EXTERNAL_STORAGE">
        <new-permission name="android.permission.READ_EXTERNAL_STORAGE" />
    </split-permission>

        android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw。 具体读文件的代码就不分析了,感兴趣的读者可以自行分析下。 这里我们可以看到读写外置存储不关联任何组。android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw,是为了兼容老版本。老版本Android 声明WRITE_MEDIA_STORAGE权限就可以读写外置存储是因为它属于media_rw组。SystemConfig.mGlobalGids里面包含AID_EVERYBODY。读过Android存储系统-使用fuse来管理外置存储都是知道android的fuse管理外置存储使用AID_EVERYBODY作为属主提供读写权限。

         到现在我们知道了应用进程启动的gids参数如何获取,下面来看下mountExternal参数如何获取。

frameworks/base/services/core/java/com/android/server/MountService.java

@Override
        public int getExternalStorageMountMode(int uid, String packageName) {
            // No locking - CopyOnWriteArrayList
            int mountMode = Integer.MAX_VALUE;
            for (ExternalStorageMountPolicy policy : mPolicies) {
                final int policyMode = policy.getMountMode(uid, packageName);
                if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
                    return Zygote.MOUNT_EXTERNAL_NONE;
                }
                mountMode = Math.min(mountMode, policyMode);
            }
            if (mountMode == Integer.MAX_VALUE) {
                return Zygote.MOUNT_EXTERNAL_NONE;
            }
            return mountMode;
        }

         mountMode主要通过ExternalStorageMountPolicy中获取, 其中一个policy就是PackageManagerService。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@Override
    public void systemReady() {
     mountServiceInternal.addExternalStoragePolicy(
                new MountServiceInternal.ExternalStorageMountPolicy() {
            @Override
            public int getMountMode(int uid, String packageName) {
                if (Process.isIsolated(uid)) {
                    return Zygote.MOUNT_EXTERNAL_NONE;
                }
                if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
                    return Zygote.MOUNT_EXTERNAL_DEFAULT;
                }
                if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
                    return Zygote.MOUNT_EXTERNAL_DEFAULT;
                }
                if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
                    return Zygote.MOUNT_EXTERNAL_READ;
                }
                return Zygote.MOUNT_EXTERNAL_WRITE;
            }

            @Override
            public boolean hasExternalStorage(int uid, String packageName) {
                return true;
            }
        });
        ......
}

         如果应用程序获取了WRITE_MEDIA_STORAGE权限则mountMode 为 MOUNT_EXTERNAL_DEFAULT,没有获取读权限则mountMode 为MOUNT_EXTERNAL_DEFAULT, 获得了读外置存储权限但是没有获得写外置存户权限则mountMode 为MOUNT_EXTERNAL_READ, 获得了读和写外置存储权限则mountMode 为 MOUNT_EXTERNAL_READ。 到此位置启动应用程序的mountExternal权限也有了。 下面来看下进程启动过程怎么设置参数。

frameworks/base/core/java/android/os/Process.java

final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

    private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // --runtime-args, --setuid=, --setgid=,
            // and --setgroups= must go first
            argsForZygote.add("--runtime-args");
            argsForZygote.add("--setuid=" + uid);
            argsForZygote.add("--setgid=" + gid);
            ......
            if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
                argsForZygote.add("--mount-external-default");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
                argsForZygote.add("--mount-external-read");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
                argsForZygote.add("--mount-external-write");
            }
            ......
            if (gids != null && gids.length > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append("--setgroups=");

                int sz = gids.length;
                for (int i = 0; i < sz; i++) {
                    if (i != 0) {
                        sb.append(',');
                    }
                    sb.append(gids[i]);
                }

                argsForZygote.add(sb.toString());
            }

        ......
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

        --setgroups= 参数指定gids, 多个gid使用逗号分割。-mount-external-read参数表示应用获取到了读外置存储权限。–mount-external-write参数表示应用获得了写外置存储权限,–mount-external-default表示应用没有获取读写外置存储权限。

         调用zygote进程的过程我们就不分析了,下面来看下zygote如何使用上述参数。

// Utility routine to fork zygote and specialize the child process.
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
                                     jint debug_flags, jobjectArray javaRlimits,
                                     jlong permittedCapabilities, jlong effectiveCapabilities,
                                     jint mount_external,
                                     jstring java_se_info, jstring java_se_name,
                                     bool is_system_server, jintArray fdsToClose,
                                     jstring instructionSet, jstring dataDir) {
    ......
  pid_t pid = fork();
  if (pid == 0) {
        
    if (!MountEmulatedStorage(uid, mount_external, use_native_bridge)) {
      ALOGW("Failed to mount emulated storage: %s", strerror(errno));
      if (errno == ENOTCONN || errno == EROFS) {
        // When device is actively encrypting, we get ENOTCONN here
        // since FUSE was mounted before the framework restarted.
        // When encrypted device is booting, we get EROFS since
        // FUSE hasn't been created yet by init.
        // In either case, continue without external storage.
      } else {
        RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage");
      }
    }
  }
  ......
  SetGids(env, javaGids);
  ......
}

         zygote 在fork子进程后,子进程还有特权,先调用MountEmulatedStorage函数来挂载外置存储,然后调用SetGids来设置进程属于的组。我们先看MountEmulatedStorage函数。

296 // Create a private mount namespace and bind mount appropriate emulated
297 // storage for the given user.
298 static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
299         bool force_mount_namespace) {
300     // See storage config details at http://source.android.com/tech/storage/
301 
302     // Create a second private mount namespace for our process
303     if (unshare(CLONE_NEWNS) == -1) {  // 1 创建一个私有的mount命令空间,脱离原来共享的命名空间。
304         ALOGW("Failed to unshare(): %s", strerror(errno));
305         return false;
306     }
307 
308     String8 storageSource;
309     if (mount_mode == MOUNT_EXTERNAL_DEFAULT) { //2 根据应用获取的权限选择使用的fuse目录。
310         storageSource = "/mnt/runtime/default";
311     } else if (mount_mode == MOUNT_EXTERNAL_READ) {
312         storageSource = "/mnt/runtime/read";
313     } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
314         storageSource = "/mnt/runtime/write";
315     } else {
316         // Sane default of no storage visible
317         return true;
318     }
319     if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",   // 3 bind /storage 到 storageSource
320             NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
321         ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno));
322         return false;
323     }
324 
325     // Mount user-specific symlink helper into place
326     userid_t user_id = multiuser_get_user_id(uid);
327     const String8 userSource(String8::format("/mnt/user/%d", user_id));
328     if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
329         return false;
330     }
331     if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", // 4 /storage/self 指向 /mnt/user/\${userid}
332             NULL, MS_BIND, NULL)) == -1) {
333         ALOGW("Failed to mount %s to /storage/self: %s", userSource.string(), strerror(errno));
334         return false;
335     }
336 
337     return true;
338 }

         上述代码分为四个步骤:
1、创建一个私有的mount命令空间,脱离原来共享的命名空间。
2、根据应用获取的权限选择使用的fuse目录。
3、 /storage 目录bind到 storageSource, 也就是之后操作/storage目录实际上都是操作storageSource。
4、/storage/self 目录bind 到 /mnt/user/${userid}

         结合我们前面的Android存储系统-MountService 和vold 对外置存储的管理(3) 和 Android存储系统-使用fuse来管理外置存储 我们可以得到一个完整的应用进程视角的目录结构。

/sdcard -> /storage/self/primary (软链接) system/core/rootdir/init.rc 中设置
/mnt/user/${userid}/primary -> /storage/emulated/${userid}/ ( 软链接) vold中用户创建时设置
/storage/self/ -> /mnt/user/${userid}/(mount bind) zygote MountEmulatedStorage 函数中设置
/storage/ -> /mnt/runtime/[default|read|write]/ ( mount bind) zygote MountEmulatedStorage 函数中设置
/mnt/runtime/[default|read|write]/emulated -> /data/media (fuse) (可能label不是emulated,这里只是拿emulated举例)。

         最终看到的主存储结构如下

/sdcard - > /storage/self/primary -> /storage/emulated/${userid}/ -> /mnt/runtime/[default|read|write]/emulated/${userid}/ -> /data/media/${userid}

        所以应用读写/sdcard目录会转到读写主存储,读写主存储会转到读写/storage/${primary_label}/${userid}/ , 读写 /storage/${primary_label}/${userid}/会被转到读写/mnt/runtime/[default|read|write]/${primary_label}/${userid}/ 这个fuse文件目录,然后fuse又把实际操作转到 主存储路径下的 media/${userid} 目录下。

        再来看下SetGids函数

// Calls POSIX setgroups() using the int[] object as an argument.
// A NULL argument is tolerated.
static void SetGids(JNIEnv* env, jintArray javaGids) {
  if (javaGids == NULL) {
    return;
  }

  ScopedIntArrayRO gids(env, javaGids);
  if (gids.get() == NULL) {
    RuntimeAbort(env, __LINE__, "Getting gids int array failed");
  }
  int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
  if (rc == -1) {
    std::ostringstream oss;
    oss << "setgroups failed: " << strerror(errno) << ", gids.size=" << gids.size();
    RuntimeAbort(env, __LINE__, oss.str().c_str());
  }
}

        实际调用setgroups来完成设置。

        最后我们来看下动态授予外置存储权限如何实现:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override
    public void grantRuntimePermission(String packageName, String name, final int userId) {
        ......
         // Only need to do this if user is initialized. Otherwise it's a new user
        // and there are no processes running as the user yet and there's no need
        // to make an expensive call to remount processes for the changed permissions.
        if (READ_EXTERNAL_STORAGE.equals(name)
                || WRITE_EXTERNAL_STORAGE.equals(name)) {
            final long token = Binder.clearCallingIdentity();
            try {
                if (sUserManager.isInitialized(userId)) {
                    MountServiceInternal mountServiceInternal = LocalServices.getService(
                            MountServiceInternal.class);
                    mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

        应用授权后如果该权限是读外置存储权限或者写外置存储权限,就会调用mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName) 通过MountService。

frameworks/base/services/core/java/com/android/server/MountService.java

private void remountUidExternalStorage(int uid, int mode) {
        waitForReady();

        String modeName = "none";
        switch (mode) {
            case Zygote.MOUNT_EXTERNAL_DEFAULT: {
                modeName = "default";
            } break;

            case Zygote.MOUNT_EXTERNAL_READ: {
                modeName = "read";
            } break;

            case Zygote.MOUNT_EXTERNAL_WRITE: {
                modeName = "write";
            } break;
        }

        try {
            mConnector.execute("volume", "remount_uid", uid, modeName);
        } catch (NativeDaemonConnectorException e) {
            Slog.w(TAG, "Failed to remount UID " + uid + " as " + modeName + ": " + e);
        }
    }

        MountService 给vold进程发送一个remount_uid命令。关于vold进程的消息处理细节在Android存储系统-MountService 和vold 对外置存储的管理(1) 这篇文章,我们直接看下vold怎样处理的remount_uid命令。

508 int VolumeManager::remountUid(uid_t uid, const std::string& mode) {
 509     LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
 510 
 511     DIR* dir;
 512     struct dirent* de;
 513     char rootName[PATH_MAX];
 514     char pidName[PATH_MAX];
 515     int pidFd;
 516     int nsFd;
 517     struct stat sb;
 518     pid_t child;
 519 
 520     if (!(dir = opendir("/proc"))) {
 521         PLOG(ERROR) << "Failed to opendir";
 522         return -1;
 523     }
 524 
 525     // Figure out root namespace to compare against below
 526     if (android::vold::SaneReadLinkAt(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) { // 1. 读/proc/1/ns/mnt, 获取init进程的mount空间名称保存到rootName
 527         PLOG(ERROR) << "Failed to readlink";
 528         closedir(dir);
 529         return -1;
 530     }
 531 
 532     // Poke through all running PIDs look for apps running as UID
 533     while ((de = readdir(dir))) { // 遍历/proc/ 下所有文件夹
 534         pidFd = -1;
 535         nsFd = -1;
 536 
 537         pidFd = openat(dirfd(dir), de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 538         if (pidFd < 0) {
 539             goto next;
 540         }
 541         if (fstat(pidFd, &sb) != 0) {
 542             PLOG(WARNING) << "Failed to stat " << de->d_name;
 543             goto next;
 544         }
 545         if (sb.st_uid != uid) { // 2 获取文件夹的uid
 546             goto next;
 547         }
 548 
 549         // Matches so far, but refuse to touch if in root namespace
 550         LOG(DEBUG) << "Found matching PID " << de->d_name;
 551         if (android::vold::SaneReadLinkAt(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) { //3 读取/proc/${x}/ns/mnt,获取进程的mount空间
 552             PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
 553             goto next;
 554         }
 555         if (!strcmp(rootName, pidName)) { //4 直接跳过mount空间为rootName的空间
 556             LOG(WARNING) << "Skipping due to root namespace";
 557             goto next;
 558         }
 559 
 560         // We purposefully leave the namespace open across the fork
 561         nsFd = openat(pidFd, "ns/mnt", O_RDONLY);
 562         if (nsFd < 0) {
 563             PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
 564             goto next;
560         // We purposefully leave the namespace open across the fork
 561         nsFd = openat(pidFd, "ns/mnt", O_RDONLY);  // 4 打开命名空间
 562         if (nsFd < 0) {
 563             PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
 564             goto next;
 565         }
 566 
 567         if (!(child = fork())) {
 568             if (setns(nsFd, CLONE_NEWNS) != 0) { // 5 设置下面后续操作的mount空间
 569                 PLOG(ERROR) << "Failed to setns for " << de->d_name;
 570                 _exit(1);
 571             }
 572             //6 后续操作和 zygote::MountEmulatedStorage() 基本一致 
 573             unmount_tree("/storage");  
 574 
 575             std::string storageSource;
 576             if (mode == "default") {
 577                 storageSource = "/mnt/runtime/default";
 578             } else if (mode == "read") {
 579                 storageSource = "/mnt/runtime/read";
 580             } else if (mode == "write") {
 581                 storageSource = "/mnt/runtime/write";
 582             } else {
 583                 // Sane default of no storage visible
 584                 _exit(0);
 585             }
 586             if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage",
 587                     NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
 588                 PLOG(ERROR) << "Failed to mount " << storageSource << " for "
 589                         << de->d_name;
 590                 _exit(1);
 591             }
 592 
 593             // Mount user-specific symlink helper into place
 594             userid_t user_id = multiuser_get_user_id(uid);
 595             std::string userSource(StringPrintf("/mnt/user/%d", user_id));
 596             if (TEMP_FAILURE_RETRY(mount(userSource.c_str(), "/storage/self",
 597                     NULL, MS_BIND, NULL)) == -1) {
 598                 PLOG(ERROR) << "Failed to mount " << userSource << " for "
 599                         << de->d_name;
 600                 _exit(1);
 601             }
 602 
 603             _exit(0);
 604         }
 605 
 606         if (child == -1) {
 607             PLOG(ERROR) << "Failed to fork";
 608             goto next;
 609         } else {
 610             TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
 611         }
 612 
 613 next:
 614         close(nsFd);
 615         close(pidFd);
 616     }
 617     closedir(dir);
 618     return 0;
 619 }

        remountUid函数的操作就是遍历/proc目录,读取/proc/${pid}/ns/mnt 目录,找到uid匹配的进程和mount空间,在该mount空间中重新绑定外置存储目录。之后应用程序就可以愉快的操作外置存储了。

        最后来看下撤掉读写外置存储权限的函数
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@Override
    public void revokeRuntimePermission(String packageName, String name, int userId) {
        ......
        killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
    }

        撤销权限的过程简单粗暴,直接杀死应用程序,因为应用程序可能已经打开了一些文件,如果不杀死,可以继续写入数据。