virtual ab方案调研
从recovery开始说起
Recovery挂载system
1 | - CreateSnapshotPartitions |
小结
对于动态分区的挂载流程,最终是按创建设备时的第一个参数device-name进行匹配查找对应的dm设备,从上述映射过程中可以发现,
没有live.snapshot时,只进行了
CreateLogicalPartition
过程,传入的device_name
就是partition_name
而有live snapshot时,第一步的
CreateLogicalPartition
过程传入的device_name
是<partition_name>-base
,再走完了MapSnapshot
过程后,最终创建的dm设备device_name
才是partition_name
。映射表,前面是base_device到cow_device的snapshot,后面是其本身的动态分区的数据。
借助动态分区的实现逻辑,既然可以有metadata描述分区信息的描述块这种东西,复用见userdata上cow_device的数据描述放在了metadata分区,然后就可以通过
dm-linear
将数据映射成dm设备。
当前的分析还只是在userspace,跟核心的实现应该是device-mapper的内核实现,dm-snapshot 和dm-snapshot-merge的内部是怎样实现的,才是virtual ab方案的真正的核心。snapshot理解
android的virtual ab方案中snapshot的方案来自于lvm(Logical Volume Manager),要理解snapshot先从lvm说起
首先可以想通过lvm大致还原snapshot创建的过程
lvm snapshot过程理解
在pc机上创建snapshot过程模拟
1 | 创建device的模拟设备 |
为原始卷建关联snapshot设备后,改变原始卷的某文件的内容,做一下对比
首先,先看下未修改的文件test_no_change的内容,在原始卷和snapshot上内容都是一样的
1 | [~/program/testlvm] - [五 4月 10, 15:33] |
而对于修改的文件 test_change
1 | ] -> sudo istat /dev/nickgroup/nicklv00 12 |
发现修改文件的inode指向的direct block内容发生了变化,且snapshot的direct block对应的块号比较小,说明其是原始的,也可以在改之前验证下,确实是这样的。
说明修改原始卷后,共享的data block的内容并没有改变,而是修改了原始卷的inode的data block索引,并将修改后的内容写入到了新的data block上。
但是又不能简单这样理解,因为lvm是逻辑卷管理,并不代表实际的物理数据也是这样的,lvm也是借助device_mapper来实现的,其extents指向关系并不能简单的从文件系统的描述data block的变化得到。不能得到原始卷中改变了的内容只是将data block块换了位置存放新内容,而snapshot的inode继续指向原始datablock的结论,因为这样是违反常识的,一旦data block被耗尽,那原始的data block的内容岂不是要被删掉,那快照还有什么意义呢。
这里先提前将snapshot的原理说下
创建快照,实际上就是将原始卷文件系统的元数据信息复制了一份(包括super block inode imap bmap group gdt)等,这里可以先验证下
1 | sudo dumpe2fs /dev/nickgroup/nicklv00_snap| sudo dumpe2fs /dev/nickgroup/nicklv00 |
实际上snapshot的原理可以理解为下面的过程:
左图为最初创建的快照数据卷状况,LVM 会预留一个区域 (比如左图的左侧三个 PE 区块) 作为数据存放处。 此时快照数据卷内并没有任何数据,而快照数据卷与源数据卷共享所有的 PE 数据, 因此你会看到快照数据卷的内容与源数据卷中的内容是一模一样的。 等到系统运行一阵子后,假设 A 区域的数据被更新了(上面右图所示),则更新前系统会将该区域的数据移动到快照数据卷中, 所以在右图的快照数据卷中被占用了一块 PE 成为 A,而其他 B 到 I 的区块则还是与源数据卷共享!
由於快照区与原本的 LV 共享很多 PE 区块,因此快照区与被快照的 LV 必须要在同一个 VG 上头,下面两点非常重要:
VG中需要预留存放快照本身的空间,不能全部被占满。
快照所在的 VG 必须与被备份的 LV 相同,否则创建快照会失败。
重要的一点是,snapshot会存在一个cow img,但原始数据改变后,会将原来的内容拷贝一份,放到这个cow img中,而且data block号会复用,建立快照的过程,就是数据映射的过程,快照一旦建立,数据的映射关系就确定下来了。快照从文件系统层面上讲也固定下来了
可以验证上述结论的实例是:
创建总大小为400M的loop设备,在这上面创建200M的原始卷,往其中写入198M大小的文件,对原始卷可以创建最小32M的snapshot,发现可以创建成功,也可以挂载snapshot,挂载后可以198M的文件是正常的,说明快照不是简单的备份关系。
而在上述过程走完后,可以修改原始卷中198M大小的文件,发现无论修改了什么内容,只要该文件被改了,那备份卷就被损坏了,同时原始卷中文件写入的操作会报空间不足的错误。
从上面snapshot的实现过程来推测,写时复制过程应该是借助device-mapper修改了物理块-逻辑块的映射关系,不然data block的复用就有问题了。
下面再看下merge过程。
合并快照的操作也需要卸载源数据卷:
确认源数据卷和快照数据卷都没有被挂载后就可以执行合并快照的操作了
1 | sudo lvconvert --merge nickgroup/nicklv00_snap |
merge过程可以看作是将snapshot的cow-img拷贝,覆盖掉改动文件的inode和data block的过程。merge之后,snapshot就变成无效的了
这个过程中牵涉到的核心点其实还是data_block的处理,因为从snapshot快照的建立到写时复制过程,如果要保证改写前后snapshot文件系统内data block块号不变,需要重定向物理扇区的过程,这里面需要device-mapper的参与。
lvm-android上snapshot
从lvm的snapshot到android上的变体,可以发现的是android上是改动了一些东西,是不是可以认为<partition_name>-cow
是snapshot复制的原始卷的元数据区,而<partition_name>-cow-img
是写时复制的数据区呢。这个有待拿到真机后再进一步验证。
理解v-ab的升级过程
下面还需要研究virtual ab打包过程的变化,以便更好理解virtual ab的升级过程。
对于升级过程, virtual ab方案使用的升级包和ab的升级包是共用的, 从build下的打包change来看,并没有直接适配virtual ab的升级包。另一方面,从update-engine的分析来看,在virtual ab的属性打开后,额外进行了一些操作:
- 根据手机中版本的信息和升级包的信息作比对, 通过文件系统级别判断中差异量的大小,根据该大小,新建一个文件,用来存放cow 区。创建欲升级分区的dm-snap镜像,放在super的尾部,存放了欲升级分区的文件系统的元数据的复制信息,形成cow-device。
- 存放cow(写时复制)区的文件,通过ioctl获取其占用的block map,和lp的extent数据结构结合, 最终形成dm-linear的索引,放到metadata分区中。
- 升级过程中(整包升级及差分升级过程),将变动的文件存放到cow区域
- 升级结束后,下次重启时,将cow-device和cow-file(写时复制区)拼接,形成snapshot-img。 对系统分区进行snapshot的映射,映射后的dm设备即为新版本。如果能正常开机,挂载dm-snapshot-merge在内核空间进行merge,merge的过程即将cow-file的内容覆盖到系统img中被修改的block块的过程。merge开始前,应该会根据需要resize动态分区。merge完成后,系统分区被更新为新版本。
kernel的实现:
kernel/msm-4.19/Documentation/device-mapper/snapshot.txt
*) snapshot
<persistent?> A snapshot of the
block device is created. Changed chunks of <chunksize> sectors will be stored on the <COW device>
. Writes will
only go to the. Reads will come from the or
fromfor unchanged data. will often be
smaller than the origin and if it fills up the snapshot will become
useless and be disabled, returning errors. So it is important to monitor
the amount of free space and expand thebefore it fills up. <persistent?> is P (Persistent) or N (Not persistent - will not survive
after reboot). O (Overflow) can be added as a persistent store option
to allow userspace to advertise its support for seeing “Overflow” in the
snapshot status. So supported store types are “P”, “PO” and “N”.The difference between persistent and transient is with transient
snapshots less metadata must be saved on disk - they can be kept in
memory by the kernel.When loading or unloading the snapshot target, the corresponding
snapshot-origin or snapshot-merge target must be suspended. A failure to
suspend the origin target could result in data corruption.snapshot-merge
takes the same table arguments as the snapshot target except it only
works with persistent snapshots.This target assumes the role of the "snapshot-origin" target and must not be loaded if the "snapshot-origin" is still present for <origin>
.Creates a merging snapshot that takes control of the changed chunks
stored in theof an existing snapshot, through a handover
procedure, and merges these chunks back into the. Once merging
has started (in the background) themay be opened and the merge
will continue while I/O is flowing to it. Changes to theare
deferred until the merging snapshot’s corresponding chunk(s) have been
merged. Once merging has started the snapshot device, associated with
the “snapshot” target, will return -EIO when accessed.
从上述文档上看, 首先snapshot-origin的角色相当与传统的lvm的snapshot机制,即文件改动时,改动的内容写到了origin区域,而原始内容备份到了cow-device上。snapshot-merge的角色相当于snapshot-origin。 snapshot的角色机制与snapshot-origin机制相反: 改动的内容写到cow-device,原始内容不变。
通过这两个机制能保证在ota升级过程中,挂载target为snapshot,改动的内容写到cow-device的cow-file中。 ota升级结束后,merge过程中,挂载类型为snapshot-merge
改动的内容写到原始区域,即系统分区空间。
从update_engine的升级过程看,以及对snapshot的实例分析,virtual ab的升级包应该和ab的升级包是同一个。差异化处理是在update_engine具体的升级过程。对snapshot的实例验证表明,snapshot的cow写时复制并不是以文件系统为基本单位的,而是以block
为基本单位,block内容变更了,才会将原来的block的内容复制到cow-file中,并重用之前的block块号,再更新block写入新的修改,在lvm上是这样处理的。对于android而言,这个过程需要反过来,所以推测android上改写了snapshot的逻辑,再检测到block块号的内容变更后,由于系统分区为只读设备,新的内容是写进了cow-file中。而源分区的数据并没有进行改动。
virtual ab recovery下的挂载流程
源码位于
1 | kernel/msm-4.19/drivers/md/dm-snap*.c |
android针对cow 镜像采用了移花接木的手段,正常recovery子系统是不能访问userdata分区的,但是cow-file又是存在于userdata分区上,而对于ab/virtual ab升级来说,虽然是在主系统升级,但是还是需要recovery子系统作为一个备胎支持ab升级的能力。所以问题是recovery子系统上如何完整的挂载snapshot呢?
这里从前面的流程分析可见,
对于某个系统分区的cow镜像来说,存在一个metadata描述
是指的metadata数据结构,其中包含了分区extents数据段,cow-file是先初始化,根据当前版本和目标版本的metadata的描述的差异,先计算cow-file需要多大的空间,直接在userdata上创建了这样一个文件,然后就可以通过ioctl命令获得其block map,将block map转化成extents,再通过dm-linear 根据extents制成dm table,最终可以创建dm设备,还原cow-file,再同cow-partition的元数据信息拼接形成完成cow partition的镜像。再通过map snapshot和源分区关联起来。