10

卷备份的实现方案探究

 9 months ago
source link: https://xuranus.github.io/2023/09/01/%E5%8D%B7%E5%A4%87%E4%BB%BD%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88%E6%8E%A2%E7%A9%B6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

虽然基于卷做的备份/恢复有较大局限性:对于文件系统使用率比较低的场景使用卷备份会导致副本存储空间利用率低,而备份的文件普遍比较大的场景下使用文件级备份也能获取不错的性能且能高效使用存储空间,但卷备份在特定场景下也有不小的优势:卷备份主机硬盘取证的领域都是常见的业务,通过备份整卷可以获得很高的备份/恢复速度,备份系统卷可以直接恢复操作系统。本文从介绍卷的基本概念出发,总结Windows/Linux平台下卷备份恢复及后续副本数据利用的几种方案。笔者基于本文提到的技术方案,自己实现了一套卷备份工具,实现了对于Windows/Linux下卷的全量备份和永久增量备份、卷副本的恢复以及卷副本的及时挂载。源码见:https://github.com/XUranus/VolumeBackup,本文可看作该项目的文档。

卷、分区、磁盘的基本概念

在开始描述卷的备份/恢复逻辑之前,先介绍一下什么是卷,因为卷(Volume)、分区(Partition)以及硬盘(Hard disk)是一组常常被混淆的概念。硬盘是物理上的概念,常用的硬盘有机械硬盘(Hard Disk Drive)和固态硬盘(Solid State Disk)等,有关硬件本文则不做详述。卷和分区都是数据的存储区域,两者相似但不相同:卷是一个拥有单一文件系统的可访问的存储区域,分区是一个硬盘上划分出的一部分。这句话意思是,分区往往是一个具体的概念,存在于具体磁盘上的某一连续的具体区域,分区可以不带文件系统(通常没有被初始化的RAW分区也算分区)。卷则是一个抽象的概念,它必须和单一文件系统强关联,且一个卷可能存在于一个或多个磁盘中。由于卷是逻辑的概念,对于Linux/Windows操作系统会表现出差异性,而物理分区则是构成逻辑卷的基础,所以欲深入理解卷又得先从分区谈起。

无论是Linux还是Windows,划分硬盘分区都使用GPT或者MBR分区表,每张磁盘都仅仅支持一个分布表用于定义磁盘布局,它们有MBR和GPT两种:

  • 主引导记录(Master Boot Record):MBR是一种Windows上常见的较老的分区模式,在硬盘头部占用512字节。由于MBR使用32字节标记逻辑块地址,则块的地址空间只有232,按照每块512B计算,MBR分区有最大 232∗512B=241B=2TB 的空间限制。MBR支持的分区个数也很有限,最大支持四个主分区(Primary Partition)或者三个主分区带一个扩展分区(Extend Partition)。使用MBR分区的系统盘只能以BIOS(Legacy)的方式引导。
  • GUID分区表(GUID Partition Table):GPT是较新的一种分区模式,在硬盘头部和尾部都占用部分空间且可以扩展。GPT能突破MBR的种种限制,支持高达128个主分区,分区大小能达到9.4ZB。使用GPT作为分区表的系统盘使用EFI引导,需要BIOS支持EFI(一般现代计算机都已经支持)。

GPT分配的每个分区都是主分区,MBR可以分配4主或者3主+1扩展,而扩展分区上又可以进一步划分逻辑分区(Logical Partition)。谈论主分区、扩展分区、逻辑分区一般都是就Windows + MBR的场景而言:

  • 主分区:最多4个。需要分配驱动器号,可以用作系统盘C:\
  • 扩展分区:最多1个,其中可以再包含若干逻辑分区。
  • 逻辑分区:只能用于存放数据信息,不可用于系统盘。可以不分配驱动器号。
volume-vs-partition-2.png

卷是建立在分区之上更加抽象的概念,一个卷可以占用一个或多个分区,可以占用不在同一块硬盘上的不同分区,所以卷拥有远超分区的可扩展性。即使MBR只支持2TB的分区,也可以通过卷跨多个分区创建更大的文件系统。由于卷是逻辑上的概念,不同操作系统对于卷的定义、实现和管理都不一致。

LDM、基础磁盘与动态磁盘

Windows引入了基础磁盘(Basic Disk)和动态磁盘(Dynamic Disk)的概念。对于Windows而言,卷和分区的显著差别是:分区是创建在基础磁盘之上的,而卷是创建在动态磁盘之上的。在Windows的磁盘管理中可以设置磁盘是基础磁盘或是动态磁盘:

volume-vs-partition-1.png

。右击磁盘可以选择将基础磁盘转变为动态磁盘,整个转化过程是无损的。相反,将动态磁盘转为基本磁盘则是不支持的(需要借助三方工具)。动态磁盘的头部和尾部区域都被写入了特定的元数据用于描述动态磁盘的分区布局以支持更强大的可扩展性。基于动态磁盘创建的卷有如下几种类型:

  • 简单卷(Simple Volume):占用一个磁盘上的一个分区。简单卷是PC上最常见的卷,通过Windows磁盘管理器右击RAW分区创建简单卷。
  • 镜像卷(Mirrored Volume):使用位于两个不同硬盘的副本来保持数据冗余,所有的写操作都会在两个磁盘上分别进行,所有的读操作则可以负载均衡到两个磁盘上以提升性能。镜像卷可以实现数据容灾,在一个硬盘不可用时依然保障数据安全。
  • 条带卷(Striped Volume):又称RAID0,使用两个以上磁盘的分区创建逻辑卷,每次写操作只会在一个磁盘上进行。这座机制提供了多通道数,可以提高I/O吞吐量。但是条带卷不提供数据冗余,不能实现容灾,且任意磁盘损坏都会导致数据丢失,是可靠性最低的一种卷。
  • 跨区卷(Spanned Volume):通过把多个磁盘的分区合并成一个逻辑卷,分区之间有先后顺序,之后之前的分区用满了才会用下一个分区。可以提升卷容量。不提供数据冗余,不能实现容灾。
  • RAID-5卷:通过使用三个及以上的磁盘,统合条带卷、跨区卷和镜像卷的优势,维护数据多副本冗余,可以在部分磁盘失效后快速恢复数据。既能提高吞吐量,又能扩展卷空间,还能容灾。

Windows上的卷一般通过LDM(Logical Disk Manager)管理,LDM基于Basic Disk/Dynamic Disk机制实现,通过在硬盘的头尾写入特定元数据,记录动态磁盘的相互关系从而构成逻辑卷。

注意:无论动态磁盘还是基础磁盘,都有各自的分区表,且都是MBR/GPT其一。

LVM与DeviceMapper

Linux上的卷一般通过LVM(Logical Volume Manager)管理,LVM提供从多个块设备创建LVM卷、管理卷组、弹性扩容、打快照等能力,其底层依赖于Linux的DeviceMapper。DeviceMapper可以从多个块设备中创建类似Windows上几类逻辑卷:mirror、stripped、linear(Windows的Spanned Volume)、RAID。此外,DeviceMapper还可以用于实现加密文件系统的加密层。有关DeviceMapper的CLI程序dmsetup的使用方法可以参考:Appendix A. The Device Mapper

DeviceMapper创建的设备的路径格式为/dev/dm-X,被软连接链接到/dev/mapper/$dmDevName

/dev/dm-X形如/dev/dm-0/dev/dm-1…. /dev/mapper/$dmDevName中的$dmDevName标记一个DeviceMapper设备的唯一名称。发送给DeviceMapper的创建的块设备路径的I/O请求会被Linux Kernel中的DeviceMapper驱动根据用户配置的规则(DM Table)映射到不同的目标磁盘的不同偏移量,创建/删除设备的控制信息则由DeviceMappper控制设备/dev/mapper/control转发。用LVM创建vg1卷组中名为lv1的逻辑卷,则会映射到/dev/mapper/vg1-lv1,通过指向对这些虚拟出的逻辑块设备执行mkfs可以初始化卷的文件系统并创建逻辑卷。

获取卷的信息

以上介绍了Windows/Linux下卷的基本概念,读者此时便可知标题为何为“卷备份”而不是“分区备份”或者“磁盘备份”。备份卷,自然只需与操作系统抽象出的卷设备交互即可,无需感知组成卷的分区在磁盘上如何分布。卷和文件系统强相关,卷才是我们真正需要备份的、有意义的概念。要备份卷设备,就是要备份卷的元数据(大小、UUID、挂载信息……)与卷中二进制数据。接下来就介绍如何操作系统平台相关API读取卷的信息。

使用POSIX API读取LINUX卷信息

Linux上所有设备被当成文件的形式映射在/dev目录下。/dev下包含块设备,字符设备,无论虚拟设备还是物理设备。SATA设备被映射为/dev/sda,/dev/sdb,/dev/sdc…..。nvme SSD设备被映射为/dev/nvmen1,/dev/nvmen2…..。对于块设备中的分区在尾部加上数字区分,例如/dev/sda1/dev/sda2……。LVM卷或者DeviceMapper管理的其他卷存放于/dev/dm-X并链接到/dev/mapper/$dmDeviceName下。

Linux上的卷设备都表现为块设备,支持按照偏移量读取和写入,而Linux又遵循“一切皆文件”的原则,所以卷可以直接当成为超大文件读写——直接使用POSIX API提供的open(),close(),read(),write(),flush(),lseek()接口即可。需要注意的是读写卷设备往往需要root用户组权限,执行备份操作以O_RDONLY模式打开并读取卷数据时卷可以处于挂载状态,但是恢复卷数据时O_RDWR模式打开并写入卷信息时卷必须解挂载!否则会报EACCES错误

Linux下获取卷大小可以用BLKGETSIZE64ioctl命令读取块设备大小,拥有卷大小后,就可以分块读取和写入了。

使用Win32 API读取Windows卷信息

NT内核也一定程度上沿袭了Linux“设备皆文件的”的设计思想:例如硬盘、光驱等设备拥有各自的设备路径:例如驱动器设备\Device\PhysicalDriveX,磁盘卷设备\Device\HarddiskVolumeX,快照卷\Device\VolumeShadowCopyX,光驱设备\Device\CdRomX……其中X表示一个自增的数字。这种\Device开头的路径为DOS路径,只为内核使用,用户态的程序一般用它们映射的软链接,例如:
\Device\HarddiskVolumeX映射为\\.\HarddiskVolumeX。用户态程序调用Win32 API的时候要注意使用用户态设备路径。

Windows下没有主动挂载的概念,对于Windows上常见的文件系统(NTFS/FAT32/ExFAT)所在的卷,只要设备被接入,文件系统就会自动被识别,可以被识别的文件系统所在的卷无论有没有获取驱动器号(Drive Letter)都可以使用卷路径直接访问文件系统。而是否自动获取驱动器号是由分区属性决定的:在Windows上可以用FindFirstVolumeW()FindNextVolumeW()以GUID格式枚举所有已经被识别的卷名,并通过QueryDosDeviceW()方法获取卷名对应的卷设备路径。Windows下使用Win32 API读写卷设备的方法和之前使用POSIX API类似,使用CreateFile()获取打开卷设备路径对应的设备句柄,然后用ReadFile()WriteFile()读写卷,最后用CloseHandle()关闭句柄。

Win32 API提供了IOCTL_DISK_GET_LENGTH_INFODeviceIoControl调用获取卷大小。有关卷的其他信息可以试用[GetVolumeInfomationByHandle()](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationbyhandlew)获取(涉及卷序列号,卷名等属性)。

快照与一致性备份

由于被挂载的卷可能在备份过程中被其他进程写入数据,导致备份生成的卷副本无法保障一致性。在文件备份中,这种不一致性往往只表现在副本中部分文件内容无意义,而卷备份副本的不一致性则是致命的:它会导致整个卷副本的数据失效。所以卷备份保障一致性无非就两种做法:

  • 解挂载卷:在备份卷设备前将卷对应的文件系统解挂载。这种方法可以保障备份的过程中卷的数据不发生变动,裸机备份就是用的这种思路。但是实际上处于生产环境的卷设备很难在业务不中断的场景下解挂载,于是用的更多的还是打快照。
  • 快照卷:Windows提供的VSS和Linux的LVM提供的快照能力可以基于某个卷创建快照卷。快照卷也是一类逻辑卷,实际备份的过程中应该从快照卷读取数据写入副本来保证卷副本的一致性。

备份引擎实现

之前章节介绍了卷的基本概念以及读写接口,为卷备份引擎的实现提供了基础。本章介绍卷的备份/恢复的主要流程,涉及全量备份、永久增量备份、全量恢复。至于不做增量备份的原因,是因为:笔者认为,卷增量副本对于数据利用的价值不大,想要挂载卷副本时需要先还原出全量副本。因此本章只讨论全量备份和永久增量备份两种方案,而恢复的副本则只需要考虑全量副本/合成全量副本。

卷备份引擎应该具有以下三种使用场景:

  • 全量备份:从卷中读取全部数据,写入副本文件,同时为卷中数据生成对应的元数据
  • 永久增量备份:从卷中读取全部数据,同全量副本数据做对比,基于上一次全量副本只覆盖写入修改部分的数据,生成合成全量副本,同时生成新的合成全量副本对应的全量元数据给下一次永久增量备份继续使用。
  • 卷恢复:从卷副本中读取数据,写入到卷设备

其中元数据包含了卷的基本信息,如:大小,挂载点,设备路径,等。为了实现永久增量备份,元数据还需要包含按块计算卷数据的对应checksum。例如,备份时将卷拆分成固定大小的block(例如4MB),然后计算这一块数据对应的SHA2 checksum,依次存下来。下一次增量备份时按照上次全量备份时副本的block大小继续拆分卷并计算对应block的SHA2并比对,将SHA2变动的块同步写入到新的合成全量副本中。备份block大小的选取需权衡性能和存储资源利用率。

备份恢复引擎

综上可以看出,卷备份/恢复的业务是一个相当简单的Read-Write流程,备份时读卷设备,写入文件;恢复时读文件,写入卷设备。而考虑到全量备份和永久增量备份的逻辑,也只需要在Read和Write之前引入一个Hash的流程用于负责生成全量的SHA2 checksum文件并在增量备份时比对先前块的checksum决定是否需要执行Write。

所以,备份引擎可以由Reader,Hasher,和Writer三个组件构成,分别用于实现读数据,对数据哈希,写入数据。haserQueue和writerQueue是分别用作Reader和Hasher以及Hasher和Writer通信的阻塞队列。Reader启动后打开一个卷或者一个副本文件,主线程不断按照4MB的大小读取数据,将数据Push进hasherQueue。Hasher订阅hasherQueue,不断Pop出数据块,并计算块对应的SHA2 checksum,如果是增量备份场景则与上一次全量副本对应位置块的checksum做比对,有差异则Push给writerQueue;如果是全量备份或者则无需比对直接Push给writerQueue。Writer启动后打开卷或者副本文件,主线程订阅writerQueue,不断Pop出数据块并写入目标位置。

ReaderHasherWriterRead 4MB DataCalculate SHA2 checksumPush Data to writerQueue (if different)Compare with previous checksumalt[Incremental Backup]Write DataReaderHasherWriter

卷恢复流程则只涉及Reader和Writer,只需要readQueue来沟通Reader和Writer即可,无需Hasher和HasherQueue参与。

VolumeBackup中这部分的实现使用了滑动窗口分段备份的形式。由于卷可能会很大,单个卷的备份时间可能会很长,将一个卷备份任务拆分成多个备份子任务,让每个子任务备份卷的一段数据,在单个子任务中记录断点并可以做到子任务失败重启提高可用性。分段备份也可以分段保存meta文件和checkpoint文件,方便一次性载入内存中。当设置副本类型为CopyFormat::BIN时分段备份产生的副本文件会按照窗口大小拆分成多个切片副本文件,可以将副本文件存放在有单个文件大小限制的文件系统上,例如FAT32。

卷副本挂载

备份后的卷数据往往以磁盘二进制镜像的形式出现,卷中的数据需要将挂载镜像成文件系统后才能访问并进行后续的数据利用,所以卷挂载是卷副本数据利用的前提。前文提到的卷备份方案为分片备份,会产生一个或多个卷的切片副本文件,所以对于此类副本的挂载方案需要支持从一个或者多个切片副本文件中挂载。本章探讨Windows和Linux下卷副本镜像的几种挂载方案的优势与局限性。

可以用于挂载的磁盘镜像文件一般有*.bin,*.iso,*.img,*.dmg等格式。其中*.bin作为一种非标准格式,没有通过的格式规范,因从也没有通用打开方式(上文提到的备份引擎产生的卷的切片副本文件即是如此,以$start.$length.copydata.bin作为名称)。而其他三种后缀名对应的格式一般有明确的定义,且分别被各大操作系支持。在讨论卷副本挂载方案之前,我们需要了解这些格式的实际定义。

  1. ISO:
    ISO文件一般是光盘的镜像,需要遵循ISO 9660标准或者UDF格式标准。ISO文件拥有头部,数据段,ISO 9660/UDF应看作一种独立的文件系统,很明显这种格式的ISO文件不适合用于通用的硬盘镜像的挂载。此类ISO文件在Windows上可以双击直接挂载并分配驱动器卷标,也可通过Win32 API调用IOCTL_CDROM_LOAD_MEDIA_IOCTL挂载。Linux上可以使用mount -o loop或者mount -t iso8660直接挂载这类文件。

也有一些ISO文件不遵循ISO 9660/UDF格式规范,他们可能是磁盘的逐扇区(Sector-By-Sector)拷贝,Windows不支持直接挂载这种文件,挂载他们需要用到三方工具例如ImDisk。这类Sector-By-Sector Copy的文件实际上应该归为IMG文件。

  1. IMG
    IMG格式一般是整个磁盘的逐扇区(Sector-By-Sector)拷贝,能完整描述整个卷的信息。Windows挂载这类文件一般需要三方软件的支持,IMG文件的挂载常见于电子取证场景,Mutiple Ways To Mount Raw Image Windows这篇文章介绍了这类文件的几种三方挂载工具,如OSFMountArsenal Image MounterImDisk等。

Linux上挂载此类文件可以先用losetup先创建一个回环设备(loop device),再用mount命令指定文件系统类型和其他挂载参数挂载对应的loop device。例如:

sudo mkdir /mnt/imagemount
sudo losetup $(losetup -f) /path/to/imagefile.img # bind /dev/loopX to imagefile.img
sudo mount -t ext4 -o ro /dev/loopX /mnt/imagemount # specify fs type, mount option, loop device and image path
  1. DMG
    DMG作为MacOS上常见的镜像格式,只在Mac系统上被支持。

通过比对几种常见镜像文件的差异,我们可以得出结论:我们在卷备份中产生的包含任意文件系统的卷副本更贴近于IMG格式。对于这类Sector-By-Sector Copy的文件,Linux上可以使用loopback device驱动挂载,Windows上则需要使用其他的方案,例如引入三方工具ImDisk。

由于我们的卷副本还引入了切片(Slice)的概念(卷副本由一个或多个保存卷不同位置的文件碎片构成),我们的挂载过程会更加复杂。接下来就在此背景下比对Linux/Windows上几种挂载卷副本方案的优劣。

Linux基于loopback device/devicemapper的挂载

Linux上可以将副本文件关联(Attach)为loopback device。loopback模块会给每一个关联的设备绑定一个/dev/loopX块设备路径(X是一个数字),设备路径可以用于随后的挂载。如果副本中只有一个镜像文件,则唯一的镜像文件包含了完整的卷,这种场景直接挂载即可:

sudo mkdir /mnt/imagemount
sudo losetup $(losetup -f) /path/to/imagefile.bin # bind /dev/loopX to imagefile.bin
sudo mount -t ext4 -o ro /dev/loopX /mnt/imagemount # specify fs type, mount option, loop device and image path

其中losetup -f用于返回一个可用的loopback设备路径,例如/dev/loop0sudo losetup /dev/loop0 /path/to/imagefile.binimagefile.bin关联到/dev/loop0上,默认sector = 512B。此时所有对于/dev/loop0的I/O都会被映射到关联的imagefile.bin文件上。

如果副本由多个镜像文件构成,例如,一个3GB的副本被拆分成3个1G的切片,分别存储在:

/path/to/imagefile.0.1073741824.bin
/path/to/imagefile.1073741824.1073741824.bin
/path/to/imagefile.2147483648.1073741824.bin

imagefile.0.1073741824.bin表示0字节开始长度1073741824字节,imagefile.1073741824.1073741824.bin表示1073741824字节开始长度1073741824字节,依次类推。

Linux中提供devicemapper驱动用于创建逻辑设备,其中创建linear设备的能力可以用于从多个卷设备中创建跨区卷(Spanned Volume)。对于上述三个文件组成的副本,可以分别先挂载成三个loopback块设备:/dev/loop0/dev/loop1/dev/loop2。每个设备分别拥有1073741824B / 512B = 2097152个sector。cli下可以用dmsetup创建linear目标设备,用[start] [length] linear [device] [offset]描述每个映射目标块设备的起始扇区扇区数类型(linear)块设备路径目标块设备起始扇区,上述样例创建名为dm-sample-tbl.txt文件:

0 2097152 linear /dev/loop0 0
2097152 2097152 linear /dev/loop1 0
4194304 2097152 linear /dev/loop2 0

然后用dmsetup create sample-volume dm-sample-tbl.txt创建名为sample-volume的dm卷,如果成功会在/dev/mapper下产生路径为/dev/mapper/sample-volume的设备。之后该设备可以用mount命令进行挂载。



Volume Copy
Check Number of Image Files
Is there only one image file?
Attach Image File to Loop Device
Mount Loop Device
Attach All Image Files to Loop Devices
Create Linear DM Device
Mount DM Device

AOSP源码中提供了使用dm-ioctl.h中的用户态API来创建、删除和查找devicemapper卷的样例,这部分代码可以参考https://android.googlesource.com/platform/system/core/+/refs/heads/main/fs_mgr/libdm

Linux上使用loopback device和devicemapper来挂载副本镜像可以做到就地挂载、零复制,可以认为是比较完美的挂载方案。

Windows基于VHD的挂载

Windows上只有满足ISO 9660/UDF的镜像可以被直接挂载,其余镜像只能以Virtual Hard Disk(VHD)文件的形式被直接挂载。VHD/VHDX是Windows提供的虚拟磁盘技术,用于从文件创建虚拟磁盘,类似Linux中的loopback device。但是相比loopback device,VHD和VHDX都拥有额外的Header和Footer,用于记录磁盘的元数据(大小,版本,等信息)。VHD/VHDX支持固定长度的卷或者动态长度的卷,其中固定长度的卷在创建VHD/VHDX文件时就在磁盘上分配相应的空间,动态卷则只创建一部分空间,后续根据写入需要按需扩展文件大小。动态磁盘文件除了基本的Header/Footer,还需要维护一张块分配表,因此拥有较复杂的文件结构。

VHD格式不能被Linux支持,且动态磁盘文件格式不能Sector-By-Sector地描述原卷信息,因此不能用作跨平台的副本存储方案。VHD可以用于较老的Windows版本,最大支持2040GB大小的卷;VHDX文件在Windows Server 2012之后支持,最大支持64TB的卷,且拥有更复杂的文件结构。VHD文件也不支持分片,因此创建挂载之前得从副本slice中创建新的VHD/VHDX文件,会有1倍的额外空间占用

Splitting a Virtual PC VHD这篇文章提到了VHD的分片方案(切片后以.v01.v02……命名)只能适用于Hyper-V虚拟机读取的场景,不能用于一般宿主主机的挂载。

VolumeBackup中备份Windows下的卷的方案是备份成VHD和VHDX格式的副本。VirtDisk.h提供了操作VHD/VHDX相关API,将数据写入虚拟磁盘文件的流程如下:

  1. 使用CreateVirtualDisk()先创建一个虚拟磁盘文件,指定磁盘大小、类型(动态磁盘或定长磁盘)。实际上磁盘大小要大于卷的大小,因为要保留一部分空间给GPT/MBR分区表,同时Windows下的GPT格式的磁盘还需要创建头部MSR(Mircosoft Reservation)分区以及磁盘尾部的2MB保留分区。
  2. OpenVirtualDisk()打开虚拟磁盘文件获得句柄,然后用AttachVirtualDisk()加载动态磁盘。此时虚拟磁盘会被分配一个磁盘设备路径\\.\PhysicalDriveX
  3. 依据磁盘设备路径打开磁盘,获取磁盘句柄,使用IOCTL_DISK_CREATE_DISK初始化磁盘为GPT格式,并用IOCTL_DISK_SET_DRIVE_LAYOUT_EX初始化MSR和副本数据分区。成功后,副本数据分区会自动被挂载并分配卷设备路径\\.\HarddiskVolumeX
  4. 使用CreateFile()打开卷设备路径获取卷设备句柄,即可使用常规的文件I/O读写卷设备了。读写完成后用DetachVirtualDisk()卸载虚拟磁盘。

从VHD/VHDX格式副本中恢复数据的流程也类似,直接AttachVirtualDisk()后获取副本卷设备路径即可。

VolumeBackup使用的GPT分区的虚拟磁盘副本的分区格式如下:

GPTPartition.drawio.png

实际测试中,VHD/VHDX格式的定长磁盘创建的时候并无大小限制,所谓的VHD 2040GB和VHDX 64TB只是针对动态磁盘而言。所以使用VHD/VHDX格式的副本存储Windows卷副本不失为一种可行的备份方案。在使用IOCTL_DISK_CREATE_DISK初始化GPT分区后需要sleep几秒后执行IOCTL_DISK_SET_DRIVE_LAYOUT_EX才能成功初始化所有分区。这一段MSDN描述的不是很清晰,笔者也没有找到解决办法:When specifying a GUID partition table (GPT) as the PARTITION_STYLE of the CREATE_DISK structure, an application should wait for the MSR partition arrival before sending the IOCTL_DISK_SET_DRIVE_LAYOUT_EX control code. For more information about device notification, see RegisterDeviceNotification. https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_disk_create_disk

Windows基于ImDisk的挂载

Windows上不需要拷贝副本且不受副本大小约束的挂载方案似乎就只剩下了使用三方工具,这里以ImDisk为例。通过阅读ImDisk源码发现,ImDisk通过在Windows内核注册驱动,创建一个虚拟磁盘,驱动程序对此虚拟磁盘的所有I/O请求转化为对应文件的I/O请求,类似于自己实现了Window下的loopback device。由于编写驱动涉及到内核开发,稍有不慎可能会造成生产环境宕机,该方案并不推荐。

参考资料:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK