系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)
一、核心问题
/data/user/<id>到底是真实目录还是挂载点?CE/DE 分离后应用数据落在哪?- 每个用户的 FBE 密钥由谁生成、何时销毁?
- 共享存储("SD 卡")在多用户下如何做到"每人看到自己的文件"?
原稿把 AOSP 7 的存储描述成"纯目录 + sdcard FUSE 守护进程"------这与 FBE(File-Based Encryption)时代的真实布局差距很大。本篇按 Environment 目录布局 → FBE 密钥 → 共享存储 → 应用路径 的顺序展开。
二、目录布局:CE/DE 分离
源码路径 :frameworks/base/core/java/android/os/Environment.java
java
public class Environment {
private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
/** Return the system directory for a user. ... */
@Deprecated
public static File getUserSystemDirectory(int userId) {
return new File(new File(getDataSystemDirectory(), "users"), Integer.toString(userId));
}
/** Returns the base directory for per-user system directory, credential encrypted. */
public static File getDataSystemCeDirectory(int userId) {
return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId));
}
public static File getDataSystemDeDirectory(int userId) {
return buildPath(getDataDirectory(), "system_de", String.valueOf(userId));
}
public static File getDataMiscCeDirectory(int userId) {
return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId));
}
public static File getDataMiscDeDirectory(int userId) {
return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId));
}
public static File getDataUserCeDirectory(String volumeUuid, int userId) {
return new File(getDataUserCeDirectory(volumeUuid), String.valueOf(userId));
}
public static File getDataUserDeDirectory(String volumeUuid, int userId) {
return new File(getDataUserDeDirectory(volumeUuid), String.valueOf(userId));
}
// ...
}
/data 下多用户相关布局:
/data
├── system/users/<id>/ 每用户系统配置(UMS 的 <id>.xml 在 system/users/ 下)
├── system_ce/<id>/ 每用户系统配置(CE:解锁后可访问)
├── system_de/<id>/ 每用户系统配置(DE:开机即可访问)
├── misc_ce/<id>/ misc_de/<id>/ 每用户服务杂项数据
├── user/<id>/<pkg>/ 应用数据 CE 区(真实目录)
└── user_de/<id>/<pkg>/ 应用数据 DE 区(真实目录)
关键设计 :CE(Credential Encrypted)目录只有用户解锁后 才可访问;DE(Device Encrypted)目录开机即可访问 (但用设备密钥加密)。应用常规数据在 CE 区,开机即需运行的服务数据放 DE 区。
getUserSystemDirectory已被标记@Deprecated,注释明确建议迁移到 CE/DE 版本。
应用看到的 /data/user/
应用代码里拿到的 Environment.getDataUserCeDirectory 是 /data/user/<id>,但它不是真实目录------/data/user 本身是一个符号链接/挂载点 ,指向 FBE 卷上的 <id>_ce 分区(ext4 独立分区,FBE 密钥保护)。对应用透明:应用写 /data/user/<id>/<pkg>/files,内核落盘到 FBE 分区。
关键设计 :"目录"只是视图,"FBE 分区"才是实体。这保证:密钥销毁(第三篇
destroyUserKey)后,整个分区即不可读------数据删除是"销毁密钥"级别的彻底。
三、FBE 用户密钥:MountService 与 vold
第三篇创建链路里的 storage.createUserKey(userId, serialNumber, isEphemeral),实际由 MountService 实现(AOSP 7 中存储管理服务即 MountService,不是原稿写的 StorageManagerService):
源码路径 :frameworks/base/services/core/java/com/android/server/MountService.java
java
final class MountService extends IStorageManager.Stub
implements Handler.Callback {
// ...
@Override
public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
try {
mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber,
ephemeral ? 1 : 0);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
@Override
public void destroyUserKey(int userId) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
try {
mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
@Override
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
try {
mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
userId, serialNumber, flags);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
}
关键设计 :Java 侧的存储管理是薄壳 ------全部加密操作通过
mCryptConnector委托 native 的 vold (cryptfs命令)。serialNumber随密钥生成传入(密钥与 serialNumber 绑定,恢复出厂/恢复备份时用于识别);ephemeral标记访客密钥(访客退出即销毁,数据不留盘)。
创建/删除用户时的密钥生命周期与第三篇流程对应:
createUser → cryptfs create_user_key(userId, serial, ephemeral)
→ mPm.prepareUserData → cryptfs prepare_user_storage(uuid, userId, serial, CE|DE)
removeUserState → cryptfs destroy_user_key(userId)
四、共享存储:每人一个视图
AOSP 7 的内置"SD 卡"(emulated storage)真实路径是 /data/media/<userId>/,每个用户一个目录,UID 归属隔离:
/data/media/
├── 0/ 主用户共享存储(UID 归属 media_rw:sdcard_rw,按用户 uid 区分)
├── 10/ 用户 10 共享存储
└── 11/ 用户 11 共享存储
用户可见路径 /storage/emulated/<userId> 由 init 的 fstab 挂载(/mnt/media_rw/emulated bind 到 /data/media/<id>),切换用户时由 AM 侧 updateUserConfiguration + WMS 重新挂载对应用户的 bind mount------不是 原稿描述的"Java 层启动 /system/bin/sdcard 守护进程"(mountFuse 方法在 AOSP 7 的 MountService 中不存在)。
应用的外部路径按用户拼装:
java
// Environment.java
public static File[] buildExternalStorageAppDirs(String packageName) {
return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
}
// /storage/emulated/<userId>/Android/data/<pkg>/files
关键设计 :共享存储的隔离靠三件事叠加:①
/data/media/<id>目录 + UID 权限(不同用户 UID 不同,互相不可见);② 每个用户只能看到自己 userId 的 emulated 挂载;③ MediaProvider 按 userId 过滤扫描结果。原稿"sdcard FUSE 做权限过滤"是 Android 4.x 的机制,AOSP 7 已废弃。
五、应用进程内的用户感知
5.1 Context 路径
ContextImpl.getFilesDir() 返回 /data/user/<userId>/<pkg>/files,其中 userId 取进程启动时绑定的用户(Process.myUserHandle())。系统服务需要操作其他用户时,通过 createContextAsUser / createPackageContextAsUser 拿到目标用户的 Context------路径自动指向对应用户的目录:
java
// ContextImpl.java(@hide API)
public Context createContextAsUser(UserHandle user, int flags) {
// 校验:INTERACT_ACROSS_USERS(_FULL) 权限
final int userId = user.getIdentifier();
// ... 权限检查 ...
return createPackageContextAsUser(getOpPackageName(), 0, user);
}
关键设计 :跨用户 Context 是系统服务操作他用户数据(如"为用户 10 的某应用发通知")的标准姿势,权限门槛
INTERACT_ACROSS_USERS(_FULL)与第九篇的跨用户权限体系一致。
5.2 数据目录的属主
/data/user/<id>/<pkg> 的属主 UID 是 userId * 100000 + appId(第二篇的 UID 映射):同一应用的两个用户实例 UID 不同,文件系统权限天然隔离。SELinux 的 app_data_file 上下文 + per-app MLS 级别(mls)进一步保证"即使 UID 相同也跨不出自己的应用数据"。
关键设计:隔离是三层叠加------UID(Unix 权限)→ SELinux(MAC)→ FBE(分区密钥)。任一层被突破,其余两层仍兜底。
六、调试
bash
# 查看 /data 下各用户目录
adb shell ls -l /data/user/ /data/media/
# 查看 FBE 卷状态(vold 管理)
adb shell dumpsys mount | grep -A5 "User"
# 查看挂载点
adb shell mount | grep -E "emulated|user"
七、本篇总结
- 目录布局 :CE/DE 分离(
system_ce/system_de/misc_ce/misc_de/user/user_de),应用数据真实落在 FBE 独立分区; - 密钥 :
MountService(IStorageManager 实现)通过 voldcryptfs命令管理用户密钥,serialNumber绑定、ephemeral支持访客; - 共享存储 :
/data/media/<id>+ UID 权限 + 按用户 bind 挂载,无 FUSE 守护进程; - 应用路径 :
/data/user/<id>/<pkg>属主 UID 跨用户唯一,三层隔离(UID/SELinux/FBE); - 跨用户访问 :
createContextAsUser+INTERACT_ACROSS_USERS权限。
下一篇:系统服务如何"知道"该为谁干活------mH 消息路由、per-user 锁、ActivityManagerInternal 的用户维度状态。