声明:本文需要一定的虚拟化相关知识
vss(Volume Shadow Copy),是Windows下用于实现数据备份恢复的技术。本文要讲的是利用这种技术实现Windows系统的热迁移功能。
所谓热迁移,就是在操作系统处于正常运行的状态下,将系统迁移到指定IP的服务端,迁移内容除了磁盘上的数据,还包括内存当中的数据,内存数据的实时同步目前暂时不在本文中介绍。
vss能够实现在特定的时刻对操作系统上所有的磁盘分区制作卷影,也就是打一个快照。我们可以使用多数对原始分区有效的Windows系统api来访问快照,也就是说,快照同样可以被视为一个设备来进行数据读写操作,只是这个设备不会再有脏数据产生。
使用Windows自带的vssadmin命令可以查看快照(vssadmin list shadows)以及其他信息。
下面先贴出我这边在win7x86、2008x86以及2003x64系统上测试通过的快照代码:
/**
* @brief 卷影复制demo
* @author mrfang
* @date 2015.11.18
*/
#include <Windows.h>
#include <tchar.h>
#include <shlwapi.h>
#include <vss.h>
#include <vswriter.h>
#include <vsbackup.h>
#include <VsProv.h>
#define LOG_BUFFER_SIZE (4096 * 2)
#define LogDebug mrlog
#define LogInfo mrlog
#define LogWarn mrlog
#define LogError mrlog
#define BUFFER_SIZE (4096)
#pragma comment (lib, "ole32.lib")
#pragma comment (lib, "VssApi.lib")
#pragma comment (lib, "Advapi32.lib")
// Helper macros to print a GUID using printf-style formatting
#define WSTR_GUID_FMT _T("{%.8x-%.4x-%.4x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x}")
#define GUID_PRINTF_ARG( X ) \
(X).Data1, \
(X).Data2, \
(X).Data3, \
(X).Data4[0], (X).Data4[1], (X).Data4[2], (X).Data4[3], \
(X).Data4[4], (X).Data4[5], (X).Data4[6], (X).Data4[7]
void mrlog(const TCHAR* format, ...)
{
TCHAR szLogBuf[LOG_BUFFER_SIZE] = {0};
va_list arg_ptr;
va_start(arg_ptr, format);
_vsntprintf_s(szLogBuf, sizeof(szLogBuf)/sizeof(szLogBuf[0]), format, arg_ptr);
va_end(arg_ptr);
_tprintf(_T("%s\n"), szLogBuf);
}
void ReleaseInterface(IUnknown* unkn)
{
if (unkn)
{
unkn->Release();
unkn = NULL;
}
}
/**
* @brief 对指定逻辑卷创建卷影
* @param [in]szVolumeName:逻辑卷名
* @param [in]snapshotSetId:卷影副本集ID,此ID在AddToSnapshotSet后会修改为对应卷影副本的ID
*/
BOOL CreateSnapshot(_In_ IVssBackupComponents* pBackup, _In_ const TCHAR* szVolumeName)
{
if (!pBackup)
{
LogError(_T("[CreateSnapshot]Invalid param"));
return FALSE;
}
HRESULT hResult = S_OK;
BOOL bRetVal = TRUE;
VSS_ID snapShotId = {0};
IVssAsync* pPrepare = NULL;
IVssAsync* pDoShadowCopy = NULL;
VSS_SNAPSHOT_PROP snapshotProp = {0};
hResult = pBackup->AddToSnapshotSet(const_cast<TCHAR *>(szVolumeName), GUID_NULL, &snapShotId);
if (hResult != S_OK)
{
LogError(_T("AddToSnapshotSet failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = pBackup->SetBackupState(false, false, /*VSS_BT_COPY*/VSS_BT_FULL);
if (hResult != S_OK)
{
LogError(_T("SetBackupState failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = pBackup->PrepareForBackup(&pPrepare);
if (hResult != S_OK)
{
LogError(_T("PrepareForBackup failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
LogInfo(_T("Preparing for backup..."));
hResult = pPrepare->Wait();
if (hResult != S_OK)
{
LogError(_T("IVssAsync Wait failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = pBackup->DoSnapshotSet(&pDoShadowCopy);
if (hResult != S_OK)
{
LogError(_T("DoSnapshotSet failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
LogInfo(_T("Taking snapshots..."));
hResult = pDoShadowCopy->Wait();
if (hResult != S_OK)
{
LogError(_T("IVssAsync Wait failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
LogInfo(_T("Get the snapshot device object from the properties..."));
hResult = pBackup->GetSnapshotProperties(snapShotId, &snapshotProp);
if (hResult != S_OK)
{
LogError(_T("GetSnapshotProperties failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
LogDebug(_T(" Snapshot Id :") WSTR_GUID_FMT, GUID_PRINTF_ARG( snapshotProp.m_SnapshotId));
LogDebug(_T(" Snapshot Set Id ") WSTR_GUID_FMT, GUID_PRINTF_ARG(snapshotProp.m_SnapshotSetId));
LogDebug(_T(" Provider Id ") WSTR_GUID_FMT, GUID_PRINTF_ARG(snapshotProp.m_ProviderId));
LogDebug(_T(" OriginalVolumeName : %ls"), snapshotProp.m_pwszOriginalVolumeName);
if (snapshotProp.m_pwszExposedName)
LogDebug(_T(" ExposedName : %ls"), snapshotProp.m_pwszExposedName);
if (snapshotProp.m_pwszExposedPath)
LogDebug(_T(" ExposedPath : %ls"), snapshotProp.m_pwszExposedPath);
if (snapshotProp.m_pwszSnapshotDeviceObject)
LogDebug(_T(" DeviceObject : %ls"), snapshotProp.m_pwszSnapshotDeviceObject);
VssFreeSnapshotProperties(&snapshotProp);
bRetVal = TRUE;
TheEnd:
ReleaseInterface(pPrepare);
ReleaseInterface(pDoShadowCopy);
return bRetVal;
}
/**
* @brief 创建卷影集
* @param [out]创建所得卷影集ID
* @return TRUE:创建成功;FALSE:创建失败
*/
BOOL CreateSnapshotSet(_Out_ IVssBackupComponents** pBackup,
_Out_ VSS_ID* snapshotSetId)
{
if (!pBackup || !snapshotSetId)
{
LogError(_T("[CreateSnapshotSet]Invalid param"));
return FALSE;
}
IVssAsync *pAsync = NULL;
HRESULT hResult = S_OK;
BOOL bRetVal = TRUE;
BOOL bFreeMetaData = FALSE;
hResult = CoInitialize(NULL);
if (hResult != S_OK)
{
LogError(_T("CoInitialize failed, code=0x%x"), hResult);
return FALSE;
}
hResult = CreateVssBackupComponents(pBackup); //Release if no longer needed
if (hResult != S_OK)
{
LogError(_T("CreateVssBackupComponents failed, code=0x%x"), hResult);
return FALSE;
}
hResult = (*pBackup)->InitializeForBackup();
if (hResult != S_OK)
{
LogError(_T("InitializeForBackup failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = (*pBackup)->SetContext(VSS_CTX_BACKUP);
if (hResult != S_OK)
{
LogError(_T("IVssBackupComponents SetContext failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
// Prompts each writer to send the metadata they have collected
hResult = (*pBackup)->GatherWriterMetadata(&pAsync);
bFreeMetaData = TRUE;
if (hResult != S_OK)
{
LogError(_T("GatherWriterMetadata failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = pAsync->Wait();
if (hResult != S_OK)
{
LogError(_T("IVssAsync Wait failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
hResult = (*pBackup)->StartSnapshotSet(snapshotSetId);
if (hResult != S_OK)
{
LogError(_T("StartSnapshotSet failed, code=0x%x"), hResult);
bRetVal = FALSE;
goto TheEnd;
}
bRetVal = TRUE;
TheEnd:
if (bFreeMetaData)
(*pBackup)->FreeWriterMetadata();
return bRetVal;
}
/**
* @brief 对指定的卷列表做卷影
* @param
* @return
*/
BOOL VolumeShadow(_In_ const TCHAR* szVolumeName)
{
if (!szVolumeName)
{
LogError(_T("[CopyVolume]Invalid param"));
return FALSE;
}
VSS_ID snapshotSetID = {0};
IVssBackupComponents* pBackup = NULL;
BOOL bRetVal = TRUE;
LogInfo(_T("CreateSnapshotSet..."));
if (!CreateSnapshotSet(&pBackup, &snapshotSetID))
{
return FALSE;
}
if (!CreateSnapshot(pBackup, szVolumeName))
{
LogError(_T("CreateSnapshot failed"));
bRetVal = FALSE;
}
ReleaseInterface(pBackup);
return bRetVal;
}
int _tmain(int argc, const TCHAR* argv[])
{
if (argc != 2)
{
LogError(_T("Usage: %s volumeName. eg. %s C\\"), __FILE__, __FILE__);
return 1;
}
VolumeShadow(argv[1]);
return 0;
}
对分区做快照之后,可以获取快照属性,其中有一项snapshotProp.m_pwszSnapshotDeviceObject便是分区所对应的快照名,于是便可以将此快照作为设备打开,从0字节处开始读取快照内部的数据。
为了加快拷贝效率,Windows提供了一个VOLUME_BITMAP_BUFFER结构,如下:
typedef struct {
LARGE_INTEGER StartingLcn;
LARGE_INTEGER BitmapSize;
BYTE Buffer[1];} VOLUME_BITMAP_BUFFER,
*PVOLUME_BITMAP_BUFFER;
在MSDN上有该结构的详细描述,该结构将分区中的数据用一个位图表示,因为磁盘上的分区数据是按簇为单位来进行读写的,因此该结构中用位图中1个bit代表1个簇来表示整个分区,bit值为1则表示对应簇写有数据,为0则表示未写入数据。因此位图的大小(按字节计)=分区大小(按字节计)/分区簇大小/8(表示1个字节8bit)。
该结构可以通过DeviceIoControl(FSCTL_GET_VOLUME_BITMAP)获取。
使用此位图,可以在读取分区数据的时候,仅读取bit值为1的簇,而跳过bit值为0的簇,并且幸运的是,这个操作针对快照也是有效的。
至此,基于vss的热迁移技术,其主要技术点已介绍完毕,数据发送以及通信部分略去。
下面分享一下在做这个的过程中遇到的问题以及解决方法:
(未完待续)
转载请注明原文链接,作者保留追究相关责任的权利。