3、动态分区与逻辑卷:super 分区、Logical Volume Manager (LVM) 在车载中的应用
说到车载存储,有个话题绕不开——分区管理。
早期 Android 用静态分区表,每个分区大小写死在代码里。改一次分区大小,要重新刷 whole image。这在手机上行得通,毕竟用户刷机频率低。但车载不一样,OTA 升级频繁,分区大小说变就变。静态分区?太死板了。
所以 Google 引入了动态分区,后来又有了 LVM。今天我们就聊聊这两样东西在车载里怎么用。
3.1 为什么车载需要动态分区?
我参与的第一个车载项目,用的还是传统静态分区。当时遇到一个头疼的问题:system 分区不够用了,vendor 分区却空着一大半。想调整?得改分区表、重刷整机、重新做 OTA 包。折腾一圈下来,一周时间就没了。
你想想看,车载系统要跑 5-10 年,中间要升级多少次?每次升级,分区大小需求都可能变。静态分区根本扛不住这种变化。
动态分区就是为了解决这个问题。它把物理分区抽象成逻辑分区,大小可以动态调整。说白了,就是给分区管理加了一层「虚拟化」。
核心思想: 物理存储空间被统一管理,逻辑分区按需分配。你不再需要关心「system 分区到底多大」,只需要告诉系统「system 需要多少空间」。
3.2 Super 分区:动态分区的基石
Android 10 开始引入 super 分区。它是个「容器分区」,里面装着 system、vendor、product 等子分区。
我记得第一次看到 super 分区时,心里想:这不就是把多个分区打包成一个吗?后来深入一看,发现没那么简单。
3.2.1 Super 分区的结构
Super 分区内部使用「逻辑块」管理空间。每个子分区对应一组逻辑块,这些逻辑块在物理上可以不连续。有点像文件系统的碎片化,但管理得更精细。
// 查看 super 分区布局
$ lpdump /dev/block/by-name/super
Slot 0:
Name: system_a
Size: 2048 MB
Extents:
- [0, 1024] // 物理块范围
- [2048, 1024] // 不连续,但逻辑上连续
Name: vendor_a
Size: 512 MB
Extents:
- [1024, 512]
Slot 1:
Name: system_b
Size: 2048 MB
// ... 类似结构
看到没?system_a 的物理块是分两段的。这在静态分区里根本不可能,但动态分区完全没问题。
3.2.2 车载场景下的 super 分区配置
车载系统通常有 A/B 分区,super 分区也要支持双槽。我建议这样配置:
| 分区 | 建议大小 | 说明 |
|---|---|---|
| super | 4-8 GB | 根据系统复杂度调整,预留 20% 余量 |
| system | 1.5-3 GB | Android 框架 + 核心服务 |
| vendor | 512 MB - 1 GB | 硬件抽象层 + 驱动 |
| product | 512 MB - 1 GB | OEM 定制内容 |
| odm | 256-512 MB | 设备专属配置 |
我的经验: 车载项目里,vendor 分区经常不够用。因为车厂会塞很多私有 HAL 和服务进去。我一般会多给 vendor 留 30% 的余量,省得后期折腾。
3.3 LVM:更灵活的逻辑卷管理
Super 分区解决了「分区大小可调」的问题,但还不够灵活。比如你想把 userdata 的一部分空间挪给 system 用,super 分区就做不到了。
LVM(Logical Volume Manager)就是来补这个短板的。它把整个存储空间(包括 super、userdata、metadata 等)统一管理,形成一个「卷组」。然后从卷组里切出「逻辑卷」给各个分区用。
3.3.1 LVM 在车载中的典型架构
物理存储 (eMMC/UFS)
|
├── 物理分区 1: bootloader
├── 物理分区 2: misc
├── 物理分区 3: super (动态分区)
└── 物理分区 4: userdata (LVM 卷组)
|
├── 逻辑卷: system_ext
├── 逻辑卷: vendor
├── 逻辑卷: product
├── 逻辑卷: odm
└── 逻辑卷: cache (可选)
嗯,这里要注意:LVM 不是替代 super 分区,而是和它配合使用。Super 分区管「系统分区」,LVM 管「数据分区」。两者分工明确。
3.3.2 车载 LVM 的配置示例
我在一个项目中用过这样的配置:
// BoardConfig.mk 中的 LVM 配置
BOARD_SUPER_PARTITION_SIZE := 6442450944 // 6GB
BOARD_SUPER_PARTITION_GROUPS := group_a group_b
// 定义逻辑卷
BOARD_GROUP_GROUP_A_SIZE := 3221225472 // 3GB
BOARD_GROUP_GROUP_A_PARTITION_LIST := system_a vendor_a product_a
// LVM 卷组配置
BOARD_LVM_VOLUME_GROUP_SIZE := 8589934592 // 8GB
BOARD_LVM_LOGICAL_VOLUMES := \
system_ext:2048M \
vendor:1024M \
product:1024M \
odm:512M \
cache:1024M
我曾经踩过的坑: LVM 的元数据会占用额外空间。我算分区大小时忘了算这 4MB 的元数据开销,结果 OTA 升级时空间差一点点,导致升级失败。后来我学乖了,每次算分区大小都多留 10MB 给元数据。
3.4 动态分区与 LVM 的协同工作
实际车载系统中,super 分区和 LVM 是配合使用的。我画了个简化的流程图:
系统启动流程:
1. Bootloader 加载 boot 分区
2. 内核启动,挂载 super 分区
3. init 进程解析 super 分区表,挂载 system/vendor/product
4. 挂载 userdata 分区,激活 LVM 卷组
5. 挂载 LVM 逻辑卷(system_ext、vendor 等)
6. 启动 Android 框架
这里有个关键点:super 分区在 bootloader 阶段就要可用,所以它必须是物理分区。而 LVM 卷组在内核启动后才激活,所以适合管理数据分区。
3.4.1 OTA 升级时的分区调整
动态分区最大的优势在 OTA 时体现。假设这次升级需要给 system 多 200MB 空间:
- OTA 包解析新分区表,发现 system 需要扩容
- 系统检查 super 分区是否有空闲空间
- 如果有,直接从空闲池里划 200MB 给 system
- 如果没有,从其他分区(比如 vendor)回收空间
- 更新分区表,重启后生效
整个过程不需要重新刷机,用户无感。这在车载场景下太重要了——你总不能要求车主把车开到 4S 店去升级吧?
我个人的习惯: 每次做 OTA 包时,我都会在 super 分区里留 10-15% 的空闲空间。这样即使升级过程中需要临时调整分区大小,也有缓冲余地。别把空间用得太满,给自己留条后路。
3.5 车载场景下的特殊考量
车载和手机不一样,有几个特殊点要注意:
- 可靠性要求更高: 车规级存储芯片的寿命和稳定性要求更严。LVM 的元数据要定期备份,防止意外损坏。
- 启动时间敏感: 车载系统要求快速启动(通常 < 10 秒)。动态分区和 LVM 的初始化不能太慢。我建议把 LVM 的激活放在 init 早期,并行执行。
- 安全启动约束: Super 分区的内容要参与 dm-verity 校验。调整分区大小后,要重新计算哈希树。
- 多用户场景: 车载可能有多个用户(驾驶员、乘客),每个用户的数据隔离要用 LVM 的快照功能。
说白了,动态分区和 LVM 给车载存储管理带来了极大的灵活性。但灵活性是有代价的——你需要更精细地管理空间,更谨慎地处理边界情况。
我在项目中遇到过一个问题:LVM 卷组里的逻辑卷太多(超过 10 个),导致内核挂载时超时。后来我把一些不常用的逻辑卷合并了,才解决这个问题。所以我的建议是:逻辑卷数量控制在 6-8 个以内,别贪多。
3.6 小结
动态分区和 LVM 是车载 Android 存储管理的核心。Super 分区解决了「系统分区动态调整」的问题,LVM 解决了「数据分区灵活管理」的问题。两者配合,才能应对车载系统 5-10 年的 OTA 升级需求。
下一章,我们会聊聊存储性能优化。车载系统的存储性能瓶颈在哪里?怎么用 F2FS 和 I/O 调度器来优化?到时候细说。