对于虚拟机来说,硬盘其实就是一个文件。虚拟机里面的操作系统对硬盘的所有操作都是对该硬盘的操作。说白了就是虚拟机欺骗的操作系统,让它认贼作父。当然这个贼并没有任何的恶意,相反对于用户来说,反而是好事。比如我们备份虚拟机的硬盘就非常方便,把虚拟硬盘文件备份一下就可以了。当然,虚拟机也可以使用真实硬盘。这可是千真万确的呦。但是,很不幸的是Bochs并没有提供这种支持。
当然啦,大多数情况下,我们使用Bochs都是用来调试自己写的小操作系统。所以,也没有使用真实硬盘的需求。但是,对于各种虚拟机来说,都自成一家,分别定义了自己的虚拟硬盘文件格式。这样一来,我们使用VBox安装的操作系统就无法在Qemu里面使用,当然也无法在Bochs里面使用。当然,我们完全可以在Qemu里面再装一个嘛。硬盘空间问题先不说,Bochs里面怎么安装个Windows Xp需要多长时间啊。真的是很难以估算啊(因为我还从来没有尝试成功过,即便是我耐心的等了两三个小时)。而,有时候我们又特别想看看Windows XP的引导过程,怎么办呢?调试一下呗。用什么调试?当然是Bochs啦。但是,怎么在Bochs里面安装Windows XP啊。
很早的时候,我们想了一个办法。那就是用Qemu安装,然后再用Bochs来运行并调试。之所以这样做是因为,Qemu的一种虚拟硬盘格式和Bochs是相同的。那就是Flat类型。但是,如果想看看Vista的呢?怎么办?还用老办法?告诉你,行不通喽。因为在Qemu里面安装不了Vista(原因不再我们的讨论之列)。
怎么办呢?是不是没解了?
怎么会没解呢?别忘了Bochs可是开源的。开源就意味着你完全可以对Bochs进行改造。既然它原本不支持,那么就给他添加上嘛?(会不会很难呢?接着看吧。)
Bochs已经支持flat, concat, external, dll, sparse, vmware3, vmware4, undoable, growing, volatile, z-undoable, z-volatile等12种之多。对于各种类型的详细描述可以查看Bochs的使用手册。我们经常使用的应该就是flat和growing。不过要说的是上面列出的类型中很多已经被禁用掉了。比如dll类型,这种类型就是通过一个dll去读写一个虚拟硬盘文件。只是很可惜,该类型被禁用掉了。原因我就清楚了。因为,即便是打开这个宏后就无法编译成功。那么,我们不妨给它添加一种新的类型。
对于每个虚拟机都会有一个配置文件(例如bochsrc.bxrc)。其中,会有一行描述了硬盘的信息:
ata0-master: type=disk, mode=flat, path="c.img", cylinders=615, heads=6, spt=17
这一行的意思就是第1个IDE接口上的主硬盘(ata0-master)插的是一个硬盘(type=disk),其类型是平坦型(mode=flat)的,虚拟硬盘文件是c.img,该硬盘的CHS参数为:615,6,17。
于是,Bochs启动的时候就会执行如下代码:
【iodev\harddrv.cpp==>void bx_hard_drive_c::init(void)第282行】
if (SIM->get_param_enum("type", base)->get() == BX_ATA_DEVICE_DISK) {
BX_DEBUG(("Hard-Disk on target %d/%d",channel,device));
BX_HD_THIS channels[channel].drives[device].device_type = IDE_DISK;
sprintf(sbtext, "HD:%d-%s", channel, device?"S":"M");
BX_HD_THIS channels[channel].drives[device].statusbar_id =
bx_gui->register_statusitem(sbtext);
int cyl = SIM->get_param_num("cylinders", base)->get();
int heads = SIM->get_param_num("heads", base)->get();
int spt = SIM->get_param_num("spt", base)->get();
Bit64u disk_size = (Bit64u)cyl * heads * spt * 512;
/* instantiate the right class */
image_mode = SIM->get_param_enum("mode", base)->get();
switch (image_mode) {
case BX_ATA_MODE_FLAT:
BX_INFO(("HD on ata%d-%d: '%s' 'flat' mode ", channel, device,
SIM->get_param_string("path", base)->getptr()));
channels[channel].drives[device].hard_drive = new default_image_t();
break;
......
default:
BX_PANIC(("HD on ata%d-%d: '%s' unsupported HD mode : %s:%d", channel, device,
SIM->get_param_string("path", base)->getptr(),
atadevice_mode_names[image_mode]));
break;
}
上面的代码的意思很清楚,就是从配置文件中读出配置信息,如CHS参数。然后根据硬盘模式判断到底是哪种类型。然后将该硬盘的hard_drive指向相关的类。比如Flat类型的是default_image_t,Growing就是growing_image_t……而这些类又都有一个相同的基类device_image_t。所以,无论格式如何变化,读写接口都是相同的。如果找不到呢?那就通知用户,您的虚拟硬盘类型不支持。
现在应该就很明了了。如果我们想添加一种新的格式,只需要新定义一种类型。然后对应的对应一个相关的访问类即可。
考虑到,我们的虚拟硬盘格式可能会复杂多变。我们不妨把读写文件的代码写到一个Dll里面。这样,以后当我们需要变换硬盘格式时就很简单了。只需要把我们写的那个Dll换一下就可以了。那么,如果我们想扩展多个类型呢?反不能每多一种都改一次Bochs的源码吧?解决起来很简单,我们只需要把Dll的名字和硬盘的类型名结合起来,然后在Default里面进行处理,再通过类型名加载对应的Dll不就可以了。例如我们添加VBoxGrowing类型的硬盘其Dll就叫做VBoxGrowing.dll。为了防止重名,我们规定这些Dll都被放在程序所在的VDisk子目录下。所以,我们将上面的代码修改如下:
......
default:
BX_INFO(("HD on ata%d-%d: '%s' '%s' mode ", channel, device,
SIM->get_param_string("path", base)->getptr()));
channels[channel].drives[device].hard_drive = new dll2_image_t(SIM->get_param_string("path", base)->getptr());
break;
}
注意,上面所说的仅仅是主要改动。并不是全部改动。其实,主要是对配置文件的读取部分修改了一点,将mode的enum类型改成了string类型。
dll2_image_t就是我们用来调用对应Dll的类。定义如下:
typedef int (*dll2_open)(const char * pathname, int flag);
typedef void (*dll2_close)();
typedef ULONG64 (*dll2_seek)(ULONG64 offset, int whence);
typedef ULONG64 (*dll2_size)();
typedef long (*dll2_read)(void* buf, size_t count);
typedef long (*dll2_write)(const void* buf, size_t count);
class dll2_image_t : public device_image_t
{
public:
dll2_image_t(const char* dllName);
~dll2_image_t();
// Open a image. Returns non-negative if successful.
int open(const char* pathname){return open(pathname, O_RDWR);};
// Open an image with specific flags. Returns non-negative if successful.
int open(const char* pathname, int flags);
// Close the image.
void close(){m_close();};
// Position ourselves. Return the resulting offset from the
// beginning of the file.
Bit64s lseek(Bit64s offset, int whence){return m_seek(offset, whence);};
// Read count bytes to the buffer buf. Return the number of
// bytes read (count).
ssize_t read(void* buf, size_t count){return m_read(buf, count);};
// Write count bytes from buf. Return the number of bytes
// written (count).
ssize_t write(const void* buf, size_t count){return m_write(buf, count);};
private:
dll2_open m_open;
dll2_close m_close;
dll2_seek m_seek;
dll2_read m_read;
dll2_write m_write;
HMODULE m_hDll;
};
dll2_image_t::dll2_image_t(const char* dllName):device_image_t()
{
char szDllName[MAX_PATH] = {0};
sprintf(szDllName, "vdisk\\%s.dll", dllName);
m_hDll = LoadLibrary(szDllName);
if (m_hDll)
{
m_open = (dll2_open)GetProcAddress(m_hDll, "vdOpen");
if (!m_open)
{
BX_PANIC(("No vdOpen() in vdisk.dll!"));
}
m_close = (dll2_close)GetProcAddress(m_hDll, "vdClose");
if (!m_close)
{
BX_PANIC(("No vdClose() in vdisk.dll!"));
}
m_seek = (dll2_seek)GetProcAddress(m_hDll, "vdSeek");
if (!m_seek)
{
BX_PANIC(("No vdSeek() in vdisk.dll!"));
}
m_read = (dll2_read)GetProcAddress(m_hDll, "vdRead");
if (!m_read)
{
BX_PANIC(("No vdRead() in vdisk.dll!"));
}
m_write = (dll2_write)GetProcAddress(m_hDll, "vdWrite");
if (!m_write)
{
BX_PANIC(("No vdWrite() in vdisk.dll!"));
}
}
else
{
BX_PANIC(("Can't find out Hard Drive DLL:%s!", szDllName));
m_open = NULL;
m_close = NULL;
m_seek = NULL;
m_read = NULL;
m_write = NULL;
}
}
dll2_image_t::~dll2_image_t()
{
if (m_hDll)
{
FreeLibrary(m_hDll);
m_hDll = NULL;
}
}
int dll2_image_t::open(const char* pathname, int flag)
{
if (!m_open)
{
return -1;
}
int iRet = m_open(pathname, flag);
if (iRet < 0)
{
BX_ERROR(("Open file(%s:%d) failed!", pathname, flag));
}
dll2_size size = (dll2_size)GetProcAddress(m_hDll, "vdSize");
if (!size)
{
BX_PANIC(("No vdSize() in vdisk.dll!"));
return -1;
}
hd_size = size();
return iRet;
}
代码非常简单。就不再详细描述了。基本逻辑就是通过类型名找到对应的Dll并导出相关的函数。然后,依次调用而已。
下面我们再看看一个简单的硬盘类型格式,其实就是Flat格式。我们不妨定义它为myFlat类型。
#include
#include
HANDLE g_hFile = NULL;
char g_szFileName[MAX_PATH] = {0};
#define LOG_FILE "myFlat.log"
void LOG(LPTSTR lpszFormat, ...)
{
va_list arg;
char szBuf[1024] = {0};
va_start (arg, lpszFormat);
vsprintf (szBuf, lpszFormat, arg);
va_end (arg);
OutputDebugString(szBuf);
FILE * f = fopen(LOG_FILE, "a+");
if (f)
{
fwrite(szBuf, 1, strlen(szBuf), f);
fclose(f);
f = NULL;
}
}
int vdOpen(const char * pathname, int flag)
{
g_hFile = CreateFile(pathname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
0);
if (g_hFile == INVALID_HANDLE_VALUE)
{
LOG("myFlat::Open file %s failed!flag=%d, ErrorNO=%ld",
pathname,
flag,
GetLastError());
return -1;
}
strcpy(g_szFileName, pathname);
return (int)g_hFile;
}
void vdClose()
{
if (g_hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(g_hFile);
g_hFile = INVALID_HANDLE_VALUE;
memset(g_szFileName, 0, sizeof(g_szFileName));
}
}
ULONG64 vdSeek(ULONG64 offset, int whence)
{
LARGE_INTEGER liOffset;
liOffset.QuadPart = offset;
liOffset.LowPart = SetFilePointer(g_hFile, liOffset.LowPart, &liOffset.HighPart, whence);
if (liOffset.LowPart == INVALID_FILE_SIZE)
{
LOG("vdisk::SetFilePointer(%s, %I64u, %d) failed!", g_szFileName, liOffset.QuadPart, whence);
}
return liOffset.QuadPart;
}
#define HDDEV_HEAD "\\\\.\\physicaldrive"
ULONG64 vdSize()
{
if (strncmp(g_szFileName, HDDEV_HEAD, strlen(HDDEV_HEAD)) == 0)
{
// TODO:支持获取的大小硬盘
return 0;
}
DWORD dwHigh = 0;
LARGE_INTEGER liSize;
liSize.LowPart = GetFileSize(g_hFile, &dwHigh);
if (liSize.LowPart == INVALID_FILE_SIZE)
{
LOG("myFlat::GetFileSize(%s) failed!", g_szFileName);
return 0;
}
return liSize.QuadPart;
}
long vdRead(void* buf, size_t count)
{
DWORD dwRead = 0;
if (!ReadFile(g_hFile, buf, count, &dwRead, 0))
{
LOG("myFlat::ReadFile(%s, %lu) failed!", g_hFile, count);
return 0;
}
return dwRead;
}
long vdWrite(const void* buf, size_t count)
{
DWORD dwWrite = 0;
if (!WriteFile(g_hFile, buf, count, &dwWrite, 0))
{
LOG("myFlat::WriteFile(%s, %lu) failed!", g_hFile, count);
return 0;
}
return dwWrite;
}
同样,我也不对这段代码进行详细的注解(它们实在是太简单了,不是吗?)。需要说的是,这个格式可是支持真实硬盘的,不信的话你就把你的虚拟硬盘文件指定为:\.\physicaldrive0试一下。如果CHS参数正确的话,你肯定可以看到你操作系统的引导画面。不过,这样做是有一定危险的喔。你想想啊,你现在正在运行着的系统在哪儿啊?不就是在\.\physicaldrive0这个文件里面吗?如果再在虚拟机里面运行一遍会怎样呢?可以告诉的是,我试过,且已经很到了Windows的引导进度条,并且没有造成恶劣后果(但是,我可不保证你能和我一样好运)。
怎么样?很简单吧。源码之下了无秘密。那就赶紧动手吧。