第七板块:Android 存储体系与文件系统 | 第二十一篇:Vold 与 FUSE 存储架构
所属板块:第七板块 --- Android 存储体系与文件系统
前置知识:第二十篇中的应用签名、PMS 安全校验、Linux 文件系统(ext4/F2FS)、SELinux 安全上下文
本篇定位 :这是 Android 设备数据持久化的基石 。如果说内存是暂存的思绪,那么存储就是永久的记忆 。本篇将彻底拆解 Vold(Volume Daemon) 的存储管理架构、FUSE(Filesystem in Userspace) 的虚拟挂载机制、多用户存储隔离(Emulated Storage) 、SD 卡与 OTG 的动态挂载 、StorageManagerService 的配额管理 。我们将深入 Kernel VFS 、Native Daemon 与 Framework Service ,揭示 Android 如何在 Linux 的文件系统之上,构建一套安全、隔离、多用户的存储沙箱。全程无存储优化技巧、无文件读写指南,仅保留 Android 存储体系的底层定义与系统级调度规范。
1. 核心结论先行(Thesis Statement)
Android 的存储体系是一个基于 Linux VFS 的虚拟化层。
- Vold 的本质 :存储设备的外交官 。它是一个 Native 守护进程,负责监听内核的 uevent(如 USB 插入、SD 卡挂载),并通过 Netlink Socket 与 Kernel 通信,管理块设备(Block Device)的生命周期。
- FUSE 的本质 :用户空间的伪装者 。它允许在用户空间实现一个文件系统,并将其挂载到内核 VFS 中。Android 使用 FUSE 来实现
/storage/emulated/的多用户隔离 和权限控制,而无需修改内核。 - 存储沙箱的本质 :路径重定向 + GID 访问控制 。应用看到的
/sdcard/实际上是 FUSE 挂载点,Vold 通过 GID(Group ID) 控制应用是否能访问外部存储。 - Mount 的本质 :将物理设备映射到目录树 。这是一个从 Block Device (/dev/block/...) -> Filesystem (ext4/F2FS) -> Mount Point (/data, /storage) 的逐级映射过程。
2. 存储架构全景图
2.1 从物理介质到应用沙箱
#mermaid-svg-vmpKa4d72Ji7AO4X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vmpKa4d72Ji7AO4X .error-icon{fill:#552222;}#mermaid-svg-vmpKa4d72Ji7AO4X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vmpKa4d72Ji7AO4X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vmpKa4d72Ji7AO4X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vmpKa4d72Ji7AO4X .marker.cross{stroke:#333333;}#mermaid-svg-vmpKa4d72Ji7AO4X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vmpKa4d72Ji7AO4X p{margin:0;}#mermaid-svg-vmpKa4d72Ji7AO4X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster-label text{fill:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster-label span{color:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster-label span p{background-color:transparent;}#mermaid-svg-vmpKa4d72Ji7AO4X .label text,#mermaid-svg-vmpKa4d72Ji7AO4X span{fill:#333;color:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X .node rect,#mermaid-svg-vmpKa4d72Ji7AO4X .node circle,#mermaid-svg-vmpKa4d72Ji7AO4X .node ellipse,#mermaid-svg-vmpKa4d72Ji7AO4X .node polygon,#mermaid-svg-vmpKa4d72Ji7AO4X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vmpKa4d72Ji7AO4X .rough-node .label text,#mermaid-svg-vmpKa4d72Ji7AO4X .node .label text,#mermaid-svg-vmpKa4d72Ji7AO4X .image-shape .label,#mermaid-svg-vmpKa4d72Ji7AO4X .icon-shape .label{text-anchor:middle;}#mermaid-svg-vmpKa4d72Ji7AO4X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vmpKa4d72Ji7AO4X .rough-node .label,#mermaid-svg-vmpKa4d72Ji7AO4X .node .label,#mermaid-svg-vmpKa4d72Ji7AO4X .image-shape .label,#mermaid-svg-vmpKa4d72Ji7AO4X .icon-shape .label{text-align:center;}#mermaid-svg-vmpKa4d72Ji7AO4X .node.clickable{cursor:pointer;}#mermaid-svg-vmpKa4d72Ji7AO4X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vmpKa4d72Ji7AO4X .arrowheadPath{fill:#333333;}#mermaid-svg-vmpKa4d72Ji7AO4X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vmpKa4d72Ji7AO4X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vmpKa4d72Ji7AO4X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vmpKa4d72Ji7AO4X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vmpKa4d72Ji7AO4X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vmpKa4d72Ji7AO4X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster text{fill:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X .cluster span{color:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vmpKa4d72Ji7AO4X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vmpKa4d72Ji7AO4X rect.text{fill:none;stroke-width:0;}#mermaid-svg-vmpKa4d72Ji7AO4X .icon-shape,#mermaid-svg-vmpKa4d72Ji7AO4X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vmpKa4d72Ji7AO4X .icon-shape p,#mermaid-svg-vmpKa4d72Ji7AO4X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vmpKa4d72Ji7AO4X .icon-shape .label rect,#mermaid-svg-vmpKa4d72Ji7AO4X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vmpKa4d72Ji7AO4X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vmpKa4d72Ji7AO4X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vmpKa4d72Ji7AO4X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 挂载点
用户空间
Linux 内核
物理介质
挂载
FUSE 挂载
挂载
管理
启动
分配配额
访问文件
eMMC / UFS 闪存
SD 卡 / TF 卡
USB OTG 设备
块设备层 (Block Layer)
文件系统 (ext4 / F2FS)
虚拟文件系统 (VFS)
Vold (Volume Daemon)
FUSE Daemon (sdcardd)
PackageManagerService
应用进程
/data (内部存储)
/storage/emulated (虚拟存储)
/mnt/media_rw (原始存储)
2.2 核心组件职责表
| 组件 | 层级 | 职责 | 学术定义 |
|---|---|---|---|
| Vold | Native | 设备管理 | 监听 uevent,格式化、挂载、卸载存储设备。 |
| FUSE | Kernel/Native | 虚拟文件系统 | 在用户空间实现文件系统逻辑,提供权限过滤。 |
| StorageManagerService | Framework | 存储服务 | 管理存储卷的生命周期,提供 API 给应用。 |
| Emulated Storage | 逻辑 | 虚拟存储 | 基于 FUSE 的多用户隔离存储,替代真实的 SD 卡路径。 |
3. Vold 的存储管理
3.1 Vold 的启动与监听
Vold 在系统启动时由 init 进程启动,通过 Netlink Socket 监听内核事件。
学术定义:
- Netlink Socket :一种特殊的 Socket,用于内核与用户空间进程通信。Vold 监听
NETLINK_KOBJECT_UEVENT。 - Uevent :内核发出的事件,如
ACTION=add(设备插入)、ACTION=remove(设备拔出)。
3.2 存储卷(Volume)的生命周期
Vold 将存储设备抽象为 Volume。
#mermaid-svg-WFRsocXLewMgrXLZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WFRsocXLewMgrXLZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WFRsocXLewMgrXLZ .error-icon{fill:#552222;}#mermaid-svg-WFRsocXLewMgrXLZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WFRsocXLewMgrXLZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WFRsocXLewMgrXLZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ .marker.cross{stroke:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WFRsocXLewMgrXLZ p{margin:0;}#mermaid-svg-WFRsocXLewMgrXLZ defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-WFRsocXLewMgrXLZ g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-WFRsocXLewMgrXLZ g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-WFRsocXLewMgrXLZ g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-WFRsocXLewMgrXLZ g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-WFRsocXLewMgrXLZ .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-WFRsocXLewMgrXLZ .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-WFRsocXLewMgrXLZ .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-WFRsocXLewMgrXLZ .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-WFRsocXLewMgrXLZ .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-WFRsocXLewMgrXLZ .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-WFRsocXLewMgrXLZ .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-WFRsocXLewMgrXLZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WFRsocXLewMgrXLZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WFRsocXLewMgrXLZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WFRsocXLewMgrXLZ .edgeLabel .label text{fill:#333;}#mermaid-svg-WFRsocXLewMgrXLZ .label div .edgeLabel{color:#333;}#mermaid-svg-WFRsocXLewMgrXLZ .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-WFRsocXLewMgrXLZ .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-WFRsocXLewMgrXLZ .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-WFRsocXLewMgrXLZ .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WFRsocXLewMgrXLZ .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WFRsocXLewMgrXLZ #statediagram-barbEnd{fill:#333333;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WFRsocXLewMgrXLZ .cluster-label,#mermaid-svg-WFRsocXLewMgrXLZ .nodeLabel{color:#131300;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-WFRsocXLewMgrXLZ .note-edge{stroke-dasharray:5;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-note text{fill:black;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram-note .nodeLabel{color:black;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagram .edgeLabel{color:red;}#mermaid-svg-WFRsocXLewMgrXLZ #dependencyStart,#mermaid-svg-WFRsocXLewMgrXLZ #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-WFRsocXLewMgrXLZ .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WFRsocXLewMgrXLZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 设备插入
开始检查
检查通过,挂载
用户请求卸载
卸载完成
物理拔出
消失
Unmounted
Checking
Mounted
Unmounting
Ejecting
3.3 关键源码解析
cpp
// system/vold/VolumeManager.cpp
void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
std::string devPath = evt->findParam("DEVPATH");
std::string action = evt->getAction();
if (action == "add") {
// 发现新设备
auto vol = createVolume(devPath);
vol->create();
} else if (action == "remove") {
// 设备移除
auto vol = findVolume(devPath);
vol->destroy();
}
}
4. FUSE 与多用户存储隔离
4.1 为什么需要 FUSE?
Android 是多用户系统(主用户、访客、工作资料)。如果直接使用 /sdcard,所有用户都能看到彼此的文件。FUSE 解决了这个问题。
学术定义:
- 多用户隔离:FUSE 可以根据调用者的 UID,动态映射到不同的物理目录。
- 权限控制 :FUSE 可以在用户空间检查应用是否有
READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE权限。
4.2 Emulated Storage 的映射
Android 使用 FUSE 创建虚拟存储:
| 路径 | 实际位置 | 学术定义 |
|---|---|---|
/storage/emulated/0 |
/data/media/0 |
主用户的虚拟存储。 |
/storage/emulated/10 |
/data/media/10 |
工作资料用户的虚拟存储。 |
/mnt/user/0/emulated/0 |
/storage/emulated/0 |
绑定挂载,用于兼容旧应用。 |
4.3 FUSE Daemon (sdcardd)
sdcards 是 FUSE 的用户空间守护进程。
工作流程:
- 应用调用
open("/storage/emulated/0/file.txt")。 - Kernel VFS 将请求转发给 FUSE 内核模块。
- FUSE 内核模块通过
/dev/fuse通知sdcardd。 sdcardd检查调用者的 UID 和权限。- 如果允许,访问实际的物理文件
/data/media/0/file.txt。 - 将结果返回给 FUSE 内核模块,再返回给应用。
/data/media/0 sdcardd (FUSE Daemon) FUSE 内核 Kernel VFS 应用 (UID 10001) /data/media/0 sdcardd (FUSE Daemon) FUSE 内核 Kernel VFS 应用 (UID 10001) #mermaid-svg-ZAYf8VCzITjUKtcM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZAYf8VCzITjUKtcM .error-icon{fill:#552222;}#mermaid-svg-ZAYf8VCzITjUKtcM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZAYf8VCzITjUKtcM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZAYf8VCzITjUKtcM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZAYf8VCzITjUKtcM .marker.cross{stroke:#333333;}#mermaid-svg-ZAYf8VCzITjUKtcM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZAYf8VCzITjUKtcM p{margin:0;}#mermaid-svg-ZAYf8VCzITjUKtcM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZAYf8VCzITjUKtcM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZAYf8VCzITjUKtcM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZAYf8VCzITjUKtcM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZAYf8VCzITjUKtcM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZAYf8VCzITjUKtcM .sequenceNumber{fill:white;}#mermaid-svg-ZAYf8VCzITjUKtcM #sequencenumber{fill:#333;}#mermaid-svg-ZAYf8VCzITjUKtcM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZAYf8VCzITjUKtcM .messageText{fill:#333;stroke:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZAYf8VCzITjUKtcM .labelText,#mermaid-svg-ZAYf8VCzITjUKtcM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .loopText,#mermaid-svg-ZAYf8VCzITjUKtcM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZAYf8VCzITjUKtcM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZAYf8VCzITjUKtcM .noteText,#mermaid-svg-ZAYf8VCzITjUKtcM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZAYf8VCzITjUKtcM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZAYf8VCzITjUKtcM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZAYf8VCzITjUKtcM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZAYf8VCzITjUKtcM .actorPopupMenu{position:absolute;}#mermaid-svg-ZAYf8VCzITjUKtcM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZAYf8VCzITjUKtcM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZAYf8VCzITjUKtcM .actor-man circle,#mermaid-svg-ZAYf8VCzITjUKtcM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZAYf8VCzITjUKtcM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} open("/storage/emulated/0/file.txt") 转发请求 读取请求 检查 UID 10001 是否有权限 访问 /data/media/0/file.txt 文件内容 返回内容 返回内容 文件句柄
5. 存储权限与 GID 映射
5.1 外部存储权限
应用访问外部存储需要声明权限。
| 权限 | GID | 学术定义 |
|---|---|---|
READ_EXTERNAL_STORAGE |
AID_SDCARD_R (1028) |
允许读取 /storage/emulated。 |
WRITE_EXTERNAL_STORAGE |
AID_SDCARD_RW (1015) |
允许写入 /storage/emulated。 |
MANAGE_EXTERNAL_STORAGE |
AID_SDCARD_ALL (9997) |
允许访问所有存储(Android 11+)。 |
5.2 FUSE 的权限过滤
sdcards 在用户空间实现权限过滤。
源码逻辑:
cpp
// system/core/sdcard/sdcard.cpp
static int handle_lookup(struct fuse *fuse, struct fuse_req *req, fuse_ino_t parent, const char *name) {
// 获取调用者的 UID
uid_t uid = fuse_req_ctx(req)->uid;
// 检查是否有权限
if (!has_permission(uid, parent, name, MAY_READ)) {
fuse_reply_err(req, EACCES); // 拒绝访问
return 0;
}
// ... 允许访问
}
6. 内部存储(/data)与外部存储(/storage)
6.1 内部存储(Private)
- 路径 :
/data/data/<package_name>/ - 特性 :私有。只有应用自身(相同 UID)可以访问。
- 加密 :Android 7.0+ 默认启用 FBE (File-Based Encryption),每个应用的数据独立加密。
6.2 外部存储(Public/Shared)
- 路径 :
/storage/emulated/0/Android/data/<package_name>/ - 特性 :共享。应用可以访问自己的目录,但在 Android 11+ 无法访问其他应用的目录(Scoped Storage)。
- 隔离:FUSE 确保每个用户只能看到自己的目录。
6.3 Scoped Storage(分区存储)
Android 10+ 引入,限制应用对外部存储的访问。
学术定义:
- 媒体集合 :应用只能访问
MediaStoreAPI 提供的图片、视频、音频。 - 自有目录 :应用只能访问
/Android/data/<package_name>/。 - SAF (Storage Access Framework):通过系统选择器访问其他文件。
7. 关键源码深度解析
7.1 StorageManagerService 的挂载逻辑
java
// frameworks/base/services/core/java/com/android/server/StorageManagerService.java
private void mount(String volId) {
// 1. 获取 Volume 信息
VolumeInfo vol = mVolumes.get(volId);
// 2. 调用 Vold 挂载
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
// 3. 如果是 emulated,启动 FUSE
if (vol.type == VolumeInfo.TYPE_EMULATED) {
startFuse(vol);
}
}
7.2 FUSE 挂载命令
bash
# 实际的 FUSE 挂载命令
/sbin/sdcard \
-u 1023 \ # uid
-g 1023 \ # gid
-m 0775 \ # 权限
/data/media /storage/emulated
8. 存储系统的常见误区
| 误区 | 学术解释 |
|---|---|
/sdcard 是真实存在的 |
它是 FUSE 挂载点,物理数据在 /data/media。 |
| 应用可以随意访问 SD 卡 | Android 10+ 受 Scoped Storage 限制,不能随意访问。 |
| 卸载 SD 卡就是拔出来 | 必须先通过 Vold 卸载文件系统,否则会导致数据损坏。 |
| 内部存储比外部存储快 | 是的。内部存储通常使用 UFS/F2FS,外部 SD 卡使用较慢的 Flash。 |
9. 本篇总结(Knowledge Closure)
| 关键点 | 纯学术定义 |
|---|---|
| Vold 的本质 | 存储设备外交官,通过 Netlink 监听内核 uevent。 |
| FUSE 的本质 | 用户空间伪装者,实现多用户存储隔离和权限控制。 |
| 存储沙箱 | 路径重定向 + GID 访问控制,通过 FUSE 实现。 |
| Emulated Storage | 基于 FUSE 的虚拟存储,物理数据位于 /data/media。 |
| Scoped Storage | 分区存储,限制应用对外部存储的随意访问。 |
10. 第七板块结语
至此,第七板块:Android 存储体系与文件系统 已全部完结。
我们从 Vold 的存储管理 出发,深入 FUSE 的虚拟挂载机制 ,探索 多用户存储隔离 ,最终抵达 内部存储的加密与外部存储的沙箱。
我们揭示了 Android 存储系统的设计哲学:用虚拟层隔离用户,用加密保护隐私,用权限控制访问。
下一篇预告 :第八板块:Android 网络体系与连接管理 | 第二十二篇:ConnectivityService 与 Netd 网络架构