4、虚拟化与存储:AVF (Android Virtualization Framework) 下的存储隔离策略
各位同学,今天我们来聊聊虚拟化环境下的存储问题。说实话,车载系统里搞虚拟化,最头疼的就是存储怎么隔离。你想想看,一个芯片上跑着好几个虚拟机,有跑IVI的,有跑仪表盘的,甚至还有跑ADAS的。它们的数据要是混在一起,那可就乱套了。
Android Automotive从Android 13开始正式引入了AVF(Android Virtualization Framework)。我最早接触这个框架是在一个项目里,客户要求在同一颗SoC上同时运行Android和Linux系统。嗯,那时候踩了不少坑,今天我把这些经验整理出来,希望能帮大家少走弯路。
4.1 AVF存储架构概览
AVF的存储架构,说白了就是「宿主管理、客户隔离」。宿主(Host)拥有物理存储设备的完全控制权,而每个虚拟机(Guest)只能看到分配给它的虚拟磁盘。
我个人习惯把AVF的存储分为三个层次:
- 物理层:eMMC、UFS或者NVMe,这是真正的硬件
- 虚拟化层:通过pKVM或crosvm实现的块设备映射
- 客户层:虚拟机内部看到的/dev/vda、/dev/mmcblk等设备
这里有个关键点:AVF使用的是pKVM(Protected KVM)技术。它和传统的KVM不一样,pKVM在内存和存储层面做了更强的隔离。我在项目中遇到过一个问题,就是客户虚拟机直接访问物理块设备,结果导致宿主系统崩溃。后来才发现,pKVM要求所有块设备访问都必须经过虚拟化层转发。
核心原则:虚拟机永远不能直接操作物理存储设备。所有I/O请求必须通过Virtio-blk或Virtio-fs等虚拟化协议转发。
4.2 存储隔离的三种策略
在实际项目中,我总结出三种常用的存储隔离策略。每种策略都有它的适用场景,我们一个一个来看。
4.2.1 全盘虚拟化(Full Disk Virtualization)
这是最直接的方式。宿主系统为每个虚拟机分配一个独立的磁盘镜像文件,比如qcow2或raw格式。虚拟机看到的就是一个完整的块设备。
// 在宿主系统中创建虚拟磁盘
// 分配一个8GB的qcow2镜像给IVI虚拟机
$ qemu-img create -f qcow2 /data/avf/disks/ivi_disk.qcow2 8G
// 分配一个2GB的qcow2镜像给仪表盘虚拟机
$ qemu-img create -f qcow2 /data/avf/disks/cluster_disk.qcow2 2G
// 启动虚拟机时挂载
$ crosvm run \
--block /data/avf/disks/ivi_disk.qcow2 \
--block /data/avf/disks/cluster_disk.qcow2 \
vmlinux
这种方式的优点是隔离性最强。一个虚拟机再怎么折腾,也影响不到其他虚拟机的数据。但缺点也很明显——存储利用率低。每个虚拟机都要预分配空间,哪怕它只用了一半。
我的建议:如果对安全性要求极高,比如ADAS系统需要存储关键驾驶数据,就用全盘虚拟化。我曾经在一个ADAS项目里,客户要求存储数据不能被任何其他系统读取,全盘虚拟化+加密是最稳妥的方案。
4.2.2 共享文件系统(Shared File System)
有些场景下,多个虚拟机需要共享某些数据。比如IVI系统和仪表盘系统都需要读取导航地图数据。这时候全盘虚拟化就不太合适了,因为数据要拷贝两份。
AVF支持通过Virtio-fs实现文件系统级别的共享。宿主系统暴露一个目录给多个虚拟机,但通过权限控制实现隔离。
// 宿主系统配置共享目录
// 只读共享给所有虚拟机
$ crosvm run \
--shared-dir /data/maps:maps:type=fs:ro \
--shared-dir /data/userdata:userdata:type=fs:rw \
vmlinux
// 在虚拟机内部,挂载共享目录
# mount -t virtiofs maps /mnt/maps
# mount -t virtiofs userdata /mnt/userdata
这里要注意的是权限控制。虽然Virtio-fs支持读写权限设置,但底层的文件系统权限还是要靠宿主系统来保证。我建议在宿主系统层面使用Linux的namespace和cgroup做二次隔离。
避坑指南:我曾经遇到过一个性能问题。当多个虚拟机同时写入共享目录时,Virtio-fs的锁机制会导致严重的性能下降。后来我们改用异步写入+缓存策略才解决。如果你的场景有高并发写入需求,建议先做压力测试。
4.2.3 混合策略(Hybrid Approach)
在实际的车载项目中,我用的最多的其实是混合策略。说白了就是「系统分区隔离,数据分区共享」。系统分区用全盘虚拟化保证安全,数据分区用共享文件系统提高效率。
| 分区类型 | 隔离策略 | 典型用途 | 性能要求 |
|---|---|---|---|
| 系统分区 | 全盘虚拟化 | /system, /vendor, /product | 中等 |
| 数据分区 | 共享文件系统 | /data, /cache | 高 |
| 日志分区 | 全盘虚拟化 | /logs | 低 |
| 媒体分区 | 共享文件系统 | /media, /maps | 高 |
你看这个表格,系统分区用全盘虚拟化,是因为系统文件一旦被篡改,整个系统就废了。而媒体分区用共享文件系统,是因为地图数据、音乐文件这些,多个虚拟机都需要访问,而且数据量很大。
4.3 存储性能优化
说到性能,这是虚拟化存储的老大难问题。我在一个项目中,客户抱怨虚拟机启动速度比原生系统慢了3倍。查了半天,发现是磁盘I/O路径太长了。
AVF下的一次写操作,要经过这么几个步骤:
- 虚拟机应用发起write()系统调用
- 虚拟机内核的ext4文件系统处理
- Virtio-blk驱动封装请求
- Virtio环传输到宿主系统
- 宿主系统的Virtio-blk后端处理
- 宿主内核的文件系统或块设备层
- 最终写入物理存储
嗯,整整7步。每一步都有开销。怎么优化?我分享几个实战经验:
- 使用IO线程池:在crosvm配置中增加io线程数,默认是1个,建议改成CPU核心数
- 开启异步IO:Virtio-blk支持异步模式,能大幅提升吞吐量
- 调整缓存策略:writeback模式比writethrough快,但数据安全性差一些
// 优化后的crosvm启动配置
$ crosvm run \
--block /data/avf/disks/system.qcow2,async=on,cache=writeback \
--block /data/avf/disks/data.qcow2,async=on,cache=writeback \
--io-threads 4 \
vmlinux
一个小技巧:如果你的虚拟机对存储延迟特别敏感,比如仪表盘系统需要快速读写日志,可以考虑使用内存盘(tmpfs)作为临时存储。虽然重启后数据会丢失,但性能提升非常明显。我在一个项目中用这个方案,把日志写入延迟从50ms降到了1ms以内。
4.4 安全与加密
车载系统的存储安全,现在越来越受重视。特别是AVF场景下,多个虚拟机共享同一块物理存储,加密就变得尤为重要。
AVF支持两种加密方式:
- 文件级加密(FBE):每个文件独立加密,密钥由宿主系统管理
- 块级加密(dm-crypt/LUKS):整个虚拟磁盘加密,性能开销较大
我个人更推荐FBE。为什么呢?因为车载场景下,不同虚拟机的数据敏感度不一样。比如IVI系统的导航数据可以不用加密,但ADAS系统的驾驶数据必须加密。FBE可以做到精细化控制。
// 在虚拟机内部启用FBE
// 设置加密策略
$ vdc cryptfs enablecrypto inplace default
// 查看加密状态
$ vdc cryptfs cryptocomplete
// 为特定目录设置加密密钥
$ vdc keymaster add_key --key-type=aes-256-gcm \
--key-path=/data/encryption_keys/adas.key
注意:加密会带来性能损耗。我在一个项目中测试过,启用FBE后,随机读写性能下降了约15%。如果你的系统对性能要求极高,建议只对敏感数据分区启用加密,不要全盘加密。
4.5 实战:配置一个安全的AVF存储方案
好了,理论讲完了,我们来动手配置一个实际方案。假设我们要在一个车载平台上运行三个虚拟机:IVI、仪表盘和ADAS。
我的设计思路是这样的:
- IVI系统:全盘虚拟化,不加密,性能优先
- 仪表盘系统:全盘虚拟化+FBE加密,安全与性能平衡
- ADAS系统:全盘虚拟化+块级加密,安全第一
- 共享地图数据:Virtio-fs只读共享
// 宿主系统配置脚本
#!/bin/bash
# 创建虚拟磁盘
qemu-img create -f qcow2 /data/avf/ivi_disk.qcow2 16G
qemu-img create -f qcow2 /data/avf/cluster_disk.qcow2 4G
qemu-img create -f qcow2 /data/avf/adas_disk.qcow2 8G
# 启动虚拟机
crosvm run \
--block /data/avf/ivi_disk.qcow2,async=on,cache=writeback \
--block /data/avf/cluster_disk.qcow2,async=on,cache=writeback \
--block /data/avf/adas_disk.qcow2,async=on,cache=writethrough \
--shared-dir /data/maps:maps:type=fs:ro \
--io-threads 4 \
--mem 4096 \
vmlinux
你看,ADAS的磁盘我用了writethrough模式,虽然慢一点,但数据安全性更高。而IVI和仪表盘用writeback,性能更好。
嗯,这就是AVF下存储隔离的核心内容。说实话,虚拟化存储这块水很深,每个项目都有自己的特殊需求。但只要你掌握了全盘虚拟化、共享文件系统和混合策略这三种基本模式,再结合加密和性能优化手段,大部分场景都能应对。
下一章我们会聊聊OTA升级时的存储管理策略,那又是另一个有意思的话题了。