7、动态分区挂载机制:device-mapper、dm-linear、dm-verity
好,咱们今天聊聊动态分区里最核心的挂载机制。说白了,就是系统怎么把那些藏在super分区里的逻辑分区,变成你能用的文件系统。
我刚开始接触动态分区时,也觉得很神秘。明明只有一个super分区,怎么就能变出system、vendor、product这么多分区来?其实背后的功臣就是Linux内核的device-mapper机制。
7.1 device-mapper:虚拟块设备的基石
device-mapper是Linux内核提供的一个框架。它允许你创建一个虚拟的块设备,然后对这个虚拟设备的读写请求,会被内核重新映射到底层的一个或多个真实设备上。
你可以把它想象成一个中间层。上层应用读写/dev/block/dm-0,dm-0再根据你定义的映射规则,去操作真正的物理分区。
在Android动态分区里,device-mapper主要干三件事:
- 线性映射:把逻辑分区的虚拟块地址,映射到super分区里的物理块地址
- 完整性校验:通过dm-verity保证分区数据没有被篡改
- 快照与回滚:在OTA升级时,通过dm-snapshot实现AB分区的无缝切换
嗯,这里要注意,device-mapper的映射表是内核维护的。你可以在用户空间通过dmsetup命令来创建和管理这些映射。
7.2 dm-linear:逻辑分区到物理块的桥梁
dm-linear是device-mapper里最简单的一种映射类型。它的作用就是做地址转换——把虚拟设备的块号,加上一个偏移量,变成物理设备的块号。
举个例子。假设super分区从物理块1000开始,你的system逻辑分区在super里从块200开始,大小是500块。那么dm-linear的映射表就是:
# 格式:起始块 长度 目标设备 目标起始块
0 500 /dev/block/mmcblk0p1 1200
这表示:虚拟设备的块0到块499,对应物理设备/dev/block/mmcblk0p1(也就是super分区)的块1200到块1699。
我在项目中遇到过一个问题。有个厂商的super分区布局改了,但没更新分区表。结果dm-linear映射的偏移量还是旧的,系统直接挂载失败。排查了半天才发现是偏移量对不上。
dmsetup table查看当前的映射表。这能帮你快速确认逻辑分区到底映射到了物理设备的哪个位置。
Android系统在挂载动态分区时,会先解析super分区头部的LpMetadata,拿到每个逻辑分区的起始偏移和大小。然后调用device-mapper创建对应的dm-linear设备。最后在这个dm设备上挂载文件系统。
流程大致是这样的:
1. 读取super分区头部,获取分区表
2. 找到system逻辑分区的起始偏移和大小
3. 调用ioctl创建dm设备,设置linear映射
4. 在/dev/block/dm-0上挂载ext4或erofs文件系统
5. 挂载成功,系统就能访问/system目录了
7.3 dm-verity:启动完整性的守护者
dm-verity是另一个重要的device-mapper目标。它的作用是保证块设备上的数据没有被篡改。说白了,就是每次读数据时,都会校验哈希值。
Android从4.4开始引入dm-verity,用来保护system分区。到了动态分区时代,它依然在发挥作用。
dm-verity的工作原理是这样的:
- 在构建系统镜像时,会生成一个Merkle哈希树
- 哈希树的根哈希值会被签名,存储在vbmeta分区里
- 启动时,内核用vbmeta里的公钥验证根哈希
- 每次读取数据块,dm-verity都会沿着哈希树向上校验
你想想看,如果有人改了system分区里的某个文件,那对应的哈希值就对不上了。dm-verity在校验时就会发现,然后返回IO错误。
我曾经遇到过一个问题。有个设备刷了第三方ROM后,启动时一直报verity错误。排查后发现是vbmeta里的哈希值和system镜像对不上。说白了,就是刷了不匹配的镜像。
dm-verity的映射表比dm-linear复杂一些。它需要指定哈希设备、哈希偏移、根哈希等信息。一个典型的dm-verity映射表长这样:
# 格式:起始块 长度 目标类型 参数...
0 500000 verity 1 /dev/block/dm-0 /dev/block/mmcblk0p2 4096 4096 12345678 sha256 \
a1b2c3d4e5f6... 32 \
/dev/block/mmcblk0p3 8
这里参数很多,但核心就几个:数据设备、哈希设备、块大小、根哈希值。嗯,实际开发中你不需要手写这些,Android的fs_mgr库会帮你处理好。
7.4 动态分区挂载的完整流程
把上面这些串起来,动态分区的挂载流程就清晰了。我画了一张图,帮你理解整个过程:
从这张图你能看到,整个挂载过程是分层的。先有物理设备,然后创建dm-linear做地址映射,再在它上面叠加dm-verity做校验,最后挂载文件系统。
我个人的习惯是,在调试动态分区问题时,先确认dm-linear映射是否正确。用dmsetup table看一眼,如果映射的偏移量和大小都对,那问题大概率出在文件系统或verity上。
核心要点回顾:
- device-mapper是Linux内核的虚拟块设备框架,Android用它来实现动态分区
- dm-linear做线性地址映射,把逻辑分区映射到super分区的物理块
- dm-verity做数据完整性校验,防止分区被篡改
- 挂载顺序:物理设备 → dm-linear → dm-verity → 文件系统
好了,动态分区的挂载机制就聊到这里。说白了,就是通过device-mapper这个中间层,把物理上连续的super分区,虚拟成多个独立的逻辑分区。再加上dm-verity的保护,确保系统启动时的数据完整性。
这些机制组合在一起,才让动态分区既能灵活调整分区大小,又能保证安全性。嗯,下一节我们会聊到OTA升级时,这些机制是怎么配合工作的。
公众号:蓝海资料掘金营,微信deep3321