前言
上一篇文章,我们走马观花地介绍了bochs从启动到CPU执行流程的大体流程,里面相关数据结构没详细分析。这篇文章我们尝试来详细分析一下bochs的物理内存初始化,首先有个概念需要理清(相信来看bochs源码的人肯定会知道):bochs只负责处理物理内存,而虚拟内存是运行在bochs中运行的OS来负责的,我们只需要关注物理内存。
找到内存初始化函数
在上篇文章中,我们提到过一个比较重要的函数 bx_init_hardware(),该函数负责初始化各种硬件(内存,Iodev,CPU.....),因此物理内存的初始化就是在该函数中进行的,我们直接来看函数片段:
int bx_init_hardware()
{
......
// set up memory and CPU objects
bx_param_num_c *bxp_memsize = SIM->get_param_num(BXPN_MEM_SIZE);
Bit64u memSize = bxp_memsize->get64() * BX_CONST64(1024*1024);
bx_param_num_c *bxp_host_memsize = SIM->get_param_num(BXPN_HOST_MEM_SIZE);
Bit64u hostMemSize = bxp_host_memsize->get64() * BX_CONST64(1024*1024);
// do not allocate more host memory than needed for emulation of guest RAM
if (memSize < hostMemSize) hostMemSize = memSize;
BX_MEM(0)->init_memory(memSize, hostMemSize);
// First load the system BIOS (VGABIOS loading moved to the vga code)
BX_MEM(0)->load_ROM(SIM->get_param_string(BXPN_ROM_PATH)->getptr(),
SIM->get_param_num(BXPN_ROM_ADDRESS)->get(), 0);
....
}
memSize与hostMemSize
首先,我们这里有一个概念,memSize 与 hostMemSize,这个名字第一眼看上去特别懵逼。难道这个也有像Intel虚拟化那种guest host内存的概念?这完全不是一个层次啊,我们现在bochs是单纯模拟硬件,只应该存在物理内存啊,此时决定不去阅读代码(概念不对,阅读出来的代码从理解上也是错误的)。
然后搜了一圈,在官方文档中找到了答案,内容如下。简单来说,就是guest是虚拟机真正分配的物理内存,而host >= guest, host多出来的部分相当于内存池,支持后续guest机的动态分配内存。
4.3.6. memory
Examples:
memory: guest=512, host=256
Set the amount of physical memory you want to emulate.
guest
Set amount of guest physical memory to emulate. The default is 32MB, the maximum amount limited only by physical address space limitations.
host
Set amount of host memory you want to allocate for guest RAM emulation. It is possible to allocate less memory than you want to emulate in guest system. This will fake guest to see the non-existing memory. Once guest system touches new memory block it will be dynamically taken from the memory pool. You will be warned (by FATAL PANIC) in case guest already used all allocated host memory and wants more.
理解了这个概念,现在问题是这个值是多大呢?还记得我们在配置文件中的第一个配置为 reg:32 ,其实这个就是来配置内存大小,其用MB作为单位,下图是我们的验证:

BX_MEM(0)->init_memory(....)函数分析
首先,上来的是内存对其,强制内存必须是1MB的整数倍,很好理解,继续往下分析。
void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
// accept only memory size which is multiply of 1M
BX_ASSERT((host & 0xfffff) == 0);
BX_ASSERT((guest & 0xfffff) == 0);
....
....
}
然后来初始化结构体,这里看出我们需要之后需要关注的三个变量,actual_vector,vector,blocks。根据delete关键字很容易怀疑其物理内存分配的地址存储在actual_vector中。
void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
...
if (BX_MEM_THIS actual_vector != NULL) {
BX_INFO(("freeing existing memory vector"));
delete [] BX_MEM_THIS actual_vector;
BX_MEM_THIS actual_vector = NULL;
BX_MEM_THIS vector = NULL;
BX_MEM_THIS blocks = NULL;
}
...
...
...
}
之后,我们可以看到有关内存分配的一个函数调用,代码如下。其调用的函数名称为"分配已经对其的vector“,因此我们知道这函数分配的是内存。但是,别急着进入函数,我们来看传入的参数:host + BIOSROMSZ + EXROMSIZE+4096,后面那个4096应该是冗余,保证确保对其,host是os所用的物理内存,BIOSROMSZ表示Bios所使用的物理内存,EXROMSIZE是一段额外物理内存,查阅资料可知"Extended ROM是一个你看不见的ROM分区或者文件夹"。
void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
BX_MEM_THIS vector = alloc_vector_aligned(host + BIOSROMSZ + EXROMSIZE + 4096, BX_MEM_VECTOR_ALIGN);
BX_INFO(("allocated memory at %p. after alignment, vector=%p",
BX_MEM_THIS actual_vector, BX_MEM_THIS vector));
...
...
...
}
关于alloc_vector_aligned(..)函数,内容如下,调用new来分配一段内存,但是后面各种算术运算可能让人看的头晕,我直接说出了吧,其内存分配后存在一个内存指针p,actual_vector = p,但是vector 必须指向 p的0x1000的倍数,如下面所示:

Bit8u* BX_MEM_C::alloc_vector_aligned(Bit64u bytes, Bit64u alignment)
{
Bit64u test_mask = alignment - 1;
BX_MEM_THIS actual_vector = new Bit8u [(Bit32u)(bytes + test_mask)];
if (BX_MEM_THIS actual_vector == 0) {
BX_PANIC(("alloc_vector_aligned: unable to allocate host RAM !"));
return 0;
}
// round address forward to nearest multiple of alignment. Alignment
// MUST BE a power of two for this to work.
Bit64u masked = ((Bit64u)(BX_MEM_THIS actual_vector + test_mask)) & ~test_mask;
Bit8u *vector = (Bit8u *) masked;
// sanity check: no lost bits during pointer conversion
assert(sizeof(masked) >= sizeof(vector));
// sanity check: after realignment, everything fits in allocated space
assert(vector+bytes <= BX_MEM_THIS actual_vector+bytes+test_mask);
return vector;
}
之后我们就来初始化,从这里我们很容易理解物理内存块的内存分布,如下面所示:

void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
....
BX_MEM_THIS len = guest;
BX_MEM_THIS allocated = host;
BX_MEM_THIS rom = &BX_MEM_THIS vector[host];
BX_MEM_THIS bogus = &BX_MEM_THIS vector[host + BIOSROMSZ + EXROMSIZE];
memset(BX_MEM_THIS rom, 0xff, BIOSROMSZ + EXROMSIZE + 4096);
....
....
}
然后下面来构造变量block,每一个内存块都会在block中获取一字节的位置,类似bitmap,一一对应,可以来表示每个内存块的属性,这个很好理解,就不过多分析。
void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
Bit32u num_blocks = (Bit32u)(BX_MEM_THIS len / BX_MEM_BLOCK_LEN);
BX_INFO(("%.2fMB", (float)(BX_MEM_THIS len / (1024.0*1024.0))));
BX_INFO(("mem block size = 0x%08x, blocks=%u", BX_MEM_BLOCK_LEN, num_blocks));
BX_MEM_THIS blocks = new Bit8u* [num_blocks];
if (0) {
// all guest memory is allocated, just map it
for (idx = 0; idx < num_blocks; idx++) {
BX_MEM_THIS blocks[idx] = BX_MEM_THIS vector + (idx * BX_MEM_BLOCK_LEN);
}
BX_MEM_THIS used_blocks = num_blocks;
}
else {
// host cannot allocate all requested guest memory
for (idx = 0; idx < num_blocks; idx++) {
BX_MEM_THIS blocks[idx] = NULL;
}
BX_MEM_THIS used_blocks = 0;
}
}
之后再来初始化 memory_handlers, 这个结构在喻强老师的《Bochs项目源码分析与注释》中详细介绍过。但是现在只是初始化,我们还没有分析其行为,就分享一下图片,留下个大体印象,等到用到的时候再来具体分析。

void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
BX_MEM_THIS memory_handlers = new struct memory_handler_struct *[BX_MEM_HANDLERS];
for (idx = 0; idx < BX_MEM_HANDLERS; idx++)
BX_MEM_THIS memory_handlers[idx] = NULL;
}
之后,其初始化一些状态,这些应该是和内存属性有关的,可能牵扯cpu相关知识,比如 memor_type[i][0]表示read属性, memory_type[i][1]表示write属性,总之等我们用到了再说,现在先来了解一下基本的结构信息。注意最后有个register_state( )函数,该函数简单来讲就是构造一个链表,其具体结构先不分析了(用不到),如下是其图片:

void BX_MEM_C::init_memory(Bit64u guest, Bit64u host)
{
...
...
...
BX_MEM_THIS pci_enabled = SIM->get_param_bool(BXPN_PCI_ENABLED)->get();
BX_MEM_THIS bios_write_enabled = 0;
BX_MEM_THIS smram_available = 0;
BX_MEM_THIS smram_enable = 0;
BX_MEM_THIS smram_restricted = 0;
for (i = 0; i < 65; i++)
BX_MEM_THIS rom_present[i] = 0;
for (i = 0; i <= BX_MEM_AREA_F0000; i++) {
BX_MEM_THIS memory_type[i][0] = 0;
BX_MEM_THIS memory_type[i][1] = 0;
}
BX_MEM_THIS register_state();
}
BX_MEM(0)->load_ROM(...) 函数分析
这个代码比较简单,就是加载BIOS到我们之前分配的物理内存的ROM区,有三种可选的加载方式,注释中已经给出。其offset在原有的ROM区域经过一系列计算得到,直接调用read读进去,没有调用memory组件的相关api。
// First load the system BIOS (VGABIOS loading moved to the vga code)
BX_MEM(0)->load_ROM(SIM->get_param_string(BXPN_ROM_PATH)->getptr(),
SIM->get_param_num(BXPN_ROM_ADDRESS)->get(), 0);
//
// Values for type:
// 0 : System Bios
// 1 : VGA Bios
// 2 : Optional ROM Bios
//
void BX_MEM_C::load_ROM(const char *path, bx_phy_address romaddress, Bit8u type)
{
...
while (size > 0) {
ret = read(fd, (bx_ptr_t) &BX_MEM_THIS rom[offset], size);
if (ret <= 0) {
BX_PANIC(("ROM: read failed on BIOS image: '%s'",path));
}
size -= ret;
offset += ret;
}
...
}
总结
这篇文章我们先暂时分析到这里,我们到目前为止还只是注重其大体概念,没有深究各个变量的细节,因为其具体作用我们还没有分析到,现在还是进行一条条支线任务尝试先搞懂大体流程,关于其中的数据结构,之后分析透彻之后会专门出文章来对其进行整理。
下篇文章我们尝试来探究floppy设备的初始化以及其如何将数据映射到内存中的。

687




被折叠的 条评论
为什么被折叠?



