前言
在这篇水文中我已经提到了动态分区和Virtual A/B大概是啥,这篇文章主要记录一下Virtual A/B大概是怎么工作的
谷歌对此的解释完全令人迷惑
https://source.android.com/devices/tech/ota/virtual_ab
秋大对此的说法比谷歌更加形象,但其实也漏掉了许多重要的点(也许是为了便于一般人理解)
正文
分阶段讲一讲系统都干了些什么 (主要是基于谷歌的文档和秋大的说法进行梳理,再加上一些自己的简单测试与日志,没有牵扯到源码分析)
系统内安装更新时
主要是创建Δ分区(或文件)(谷歌称之为copy-on-write (COW) device
)
这个东西的作用是存储新系统与老系统的拆分包(新系统-老系统=Δ) (但是似乎并不是新老系统diff一下,把差异存在里面,而是根据payload里更新了哪些区块,有更新的都会往里面塞,无论内容是否相同)
为什么说是Δ分区(或文件)
呢?
谷歌写道
Location of COW files — For launch devices, the OTA client uses all available empty space in the super partition before using space in /data. For retrofit devices, there's always enough space in the super partition so that the COW file is never created on /data.
也就是说,Δ内容默认会在super分区中创建临时的逻辑分区来进行存储,而在super分区空间不够用时,才会在/data下创建
而按照谷歌的描述,Virtual A/B 实际上有两种制式,一种是出厂安卓11的原生VAB,这种制式下super分区只预留了足以装下一组逻辑分区的空间;另一种是由安卓10动态分区升级改装而来的VAB,这种制式下super分区的容量足够装下两组逻辑分区。
因此,对于原生VAB,需要data分区的空间补偿来保存Δ内容,对于第二种,super分区本身已经足够。
那么,新建的用来存储Δ内容的逻辑分区和存储Δ内容的文件长什么样呢?(来自fastbootd)
(bootloader) is-logical:system_a-cow:yes
(bootloader) is-logical:odm_a-cow:yes
(bootloader) is-logical:product_a-cow:yes
(bootloader) is-logical:system_ext_a-cow:yes
那么,文件呢?
位于
/data/gsi/ota
是类似于这样的东西
# ls /data/gsi/ota
system_ext_a-cow-img.img system_ext_a-cow-img.img.0000 vendor_a-cow-img.img vendor_a-cow-img.img.0000
嗯,一部分分区的Δ位于data,一部分位于super,看空间安排了。
在Δ内容全部生成好后,系统将会把boot control HAL的merge status
设置为SNAPSHOTTED
,并切换分区(我猜这里的切换仅仅只是重命名)
这里有个很有趣的点,比如当前你在a分区,当前system分区名为system_a
,那么生成的Δ分区名称就为system_a-cow
,而在切换分区这一操作完成后,这两个分区中的a都会变成b(笑 不愧是《Virtual》
安装完开机
此时boot control HAL的merge status
为SNAPSHOTTED
,新系统的每个动态分区都由两部分组成——老系统的分区和新旧系统拆分的Δ内容。系统在开机时需要将分区的原始内容(即上一版的系统)与Δ内容(新旧系统的拆分包)进行动态的合并,并使用这合并后的内容启动系统 (此时仅仅只是动态的合并并加载到内存中,并没有物理上的合并)
如果开机失败,那很简单,分区切回去,再次开机时不加载Δ内容,也就是使用老系统开机
如果开机成功,系统将会设置boot control HAL的merge status
为MERGING
,并在后台默默将Δ内容物理的合并进老系统的分区中,形成真正的新系统
合并完成,系统将会设置boot control HAL的merge status
为NONE
再次重新启动
此时,boot control HAL的merge status
为NONE
,纯粹由包含完整新系统的分区进行启动,没有Δ内容参与
至此,Virtual A/B更新正式完成
merge status 是干啥的?
嗯。这个很有意思,简单来说,不同的状态决定了系统要如何启动,也决定了系统的一些特性能否正常工作。
当merge status处于SNAPSHOTTED
状态时,系统通过合并Δ内容和老系统分区 来形成 新系统分区 进行启动
当merge status处于MERGING
状态时,这时很有意思,系统分区处于新老系统交替的状态,甚至根本就不是完整的文件系统,此时系统的启动相当于是通过 分区中的已合并内容 + Δ中的未合并内容 来完成的,也就是说,MERGING
过程中随便重启也是没有关系的,谷歌早就帮你考虑好了(笑
当merge status处于NONE
状态时,仅通过系统分区启动,不加载Δ内容。
有人可能会说,那我在MERGING
的时候双清会怎么样,不是有一部分Δ内容在data里么?
谷歌早就帮你想好了,这样会砖的(因为系统分区不完整)
所以,人家就,不让你双清🤣
If the merge status is MERGING, or if the merge status is SNAPSHOTTED and the slot has changed to the newly updated slot, then requests to wipe userdata, metadata, or the partition storing the merge status must be rejected in the bootloader.
所以,bootloader,fastbootd,recovery和系统,要同时都能获得merge status的内容,否则,它们之中的随便一个都可以双清系统....
这里就出现了一个奇观
Implement the boot control HAL so that the bootloader can read the value set by the setSnapshotMergeStatus() method.
bootloader这种木乃伊要活起来从HAL里读内容🤣
结
嗯,只是大概弄清了一些,但是还有许多谜团没有解开
比如
- data分区在这个过程中是怎么挂载的?难道不是要早于system分区的加载?
- merge status 存储在哪里?data里?那bootloader会挂载data?
- bootloader这种僵尸是怎么和HAL通信的?
有待进一步探索
感谢大佬这篇文章,收获很大,有个疑问请教一下,“此时仅仅只是动态的合并并加载到内存中,并没有物理上的合并”,如果super分区是8G,岂不是要在内存里分配8G空间来支撑这个系统?如果是读到哪个block就load哪个block到内存的话,那merge时修改正在运行着的super分区应该会导致系统崩溃吧,比如说ubuntu系统正在运行,这时强行 "dd if=/dev/zero of=/dev/sda",系统立马就坏掉了;期待大佬的回复
是个好问题。
“动态的合并并加载到内存中”并不是指一次性的把东西合并完然后全部加载到内存里,而是指在需要加载时进行动态的合并。我并没有研究过合并具体是如何进行的,但是我可以给出我的猜测:
Virtual A/B 本身是工作在 device-mapper 的基础上的,而 device-mapper 具有“重定向 I/O 请求”的能力。因此并不一定需要将 super 分区全部加载到内存中,我们只需要确保 I/O 请求能够落到正确的数据块上就可以了。在合并的过程中也是如此,即使系统文件正在被修改,但 I/O 请求并不会落到那些“不完整”的数据块上,而是会被重定向到正确的数据块上。所以“读到哪个block就load哪个block到内存”中的 block 并不一定全部来自一个地方,而一定来自正确的地方,可能一个 block 来自真正的系统分区,另一个 block 来自 copy-on-write 文件。
猜测不一定准确,不过在“动态合并”过程中确保数据的完整性是一定可以做到的,btrfs 就是一个很好的例子。
感谢大佬回复 ~~
有个疑问,请大神帮忙解答一下。
支持A/B分区的设备,是可以在fastboot里面进行切换的。那么虚拟A/B的设备,还有这个切换功能吗?如果A分区异常了,切换B分区有意义吗?
不完全有切换功能,切换功能仅存在于“更新完,如果启动失败,则自动回滚”这种环境下。当合并完成后,切换功能就已经丧失了,切换了之后是没法开机的。
感谢分享!
Hi 大佬,我想请问一下,Android 11后强制使用A/B分区,那么如今项目OTA升级方式是A/B还是Virtual A/B是不是依然是由生产厂商决定的呢?
这个我没有实际研究过,好几年没摸新机子了。
不过据说,三星还在用 A only ?那如果有厂商还在用 A/B 好像也不奇怪。
谢谢回复!
不可多得的好文章,前阵子本地验证ota功能的时候发现在recovery使用adb sideload 命令不能成功升级会报错。分析日志发现是super分区不够,然后/data挂载看起来异常。导致未能成功创建所有分区的snapshot,从而升级失败。
update_engine_sideload E 01-06 03:09:13 405 405 fiemap_writer.cpp:664] Failed to create file at: /data/gsi/ota/vendor_b-cow-img.img: No such file or directory