Bochs源码分析-MEM类
因为学习需要,要看虚拟机Bochs的源代码。写随笔主要为了学习总结,其次是分享大家共同研究,大神勿喷,欢迎评论。
手头资料:bochs源代码,下于:bochs.sourceforge.net,还有喻强写的源码分析电纸书。
计算机系统最主要的两个部件是CPU和内存。CPU负责不断读取内存里面的指令进行工作。一块固定大小的内存条(2G)和能由很多ROM,RAM,SDRAM通过位扩展法、字扩展法共同组合而成,有的外部设备的驱动的各种特殊功能控制器也被编址到内存空间,也就是说:内存集成了很多功能,虽然同样是内存空间的地址访问,可能对应不同的设备,有着不同的处理办法。CPU对外界的访问是通过地址线和内存线,只管把要访问的地址交与总线,具体是有南北桥这样的设备,进行地址到各种不同设备、处理办法之间的转换。Bochs需要模拟整个内存空间,就要相应的申请相同大小的变量来模拟器状态的变化,具体上Bochs把内存分成三块主存SDRAM,256KB ROM,和另外一个附加的空间(特殊功能寄存器)。具体的代码是在memory/misc_mem.cpp:init_memorty(int memsize)来实现,调用是在main函数,bx_init_hardware()中:BX_MEM(0)->init_memory(memSize);在函数中内存变量的具体申请是:alloc_vector_aligned(memsize+ BIOSROMSZ + EXROMSIZE + 4096, BX_MEM_VECTOR_ALIGN);BX_MEM_THIS rom = &BX_MEM_THIS vector[memsize];
BX_MEM_THIS bogus = &BX_MEM_THIS vector[memsize + BIOSROMSZ + EXROMSIZE];可以看出变量vector指向内存初始地址(表示主内存SDRAM),rom指向memsize向后的一段地址(表示ROM),bogus指向更后面一段(为特殊功能寄存器使用),分开处理,就是为了,模拟对不同的内存空间对用的具体操作和安全限制是不一样的。对于固定的体系架构(如X86)内存空间具体每部分地址空间的用途都已经设计好了,约定熟成,这样方便操作系统的开发,当然模拟机也要遵循这样的约定管理不同的地址空间,以便不加修改就能运行x86对应的操作系统。具体表现在mem类最主要的两个函数:memory/memory.cpp writePhysicalPage(BX_CPU_C *cpu, bx_phy_address addr, unsigned len, void *data)和readPhysicalPage(BX_CPU_C *cpu, bx_phy_address addr, unsigned len, void *data)主要工作就是对要访问的地址,按约定熟成进行划分,对应到具体的某项用途,调用相应的处理方法。其x86内存约定比如:(1m之内的内存使用)
// 0x00000 - 0x7ffff DOS area (512K)
// 0x80000 - 0x9ffff Optional fixed memory hole (128K)
// 0xa0000 - 0xbffff Standard PCI/ISA Video Mem / SMMRAM (128K)
// 0xc0000 - 0xdffff Expansion Card BIOS and Buffer Area (128K)
// 0xe0000 - 0xeffff Lower BIOS Area (64K)
// 0xf0000 - 0xfffff Upper BIOS Area (64K)
这两个函数其实是模拟了南北桥这样设备的功能。
因为各种外部设备可能把自己控制器的特殊功能控制器,映射到内存空间上(统一地址映射),统一管理,Bochs也是这样处理的, 具体是维持很多mem_handler链表,具体数据结构是:struct memory_handler_struct {
struct memory_handler_struct *next; // 下一个的pointer, 用来构成链表
unsigned long begin; // 受管理的内存端开始地址
unsigned long end; // 受管理的内存端结束地址
memory_handler_t read_handler ; // 读函数
memory_handler_t write_handler; // 写函数
void *read_param;
void *write_param;
};
可见,当Bochs新添加一个设备时,要让它的特殊功能控制器,在内存空间申请一段空间,注册相应的mem_handler链表,说明申请空间的地址范围,和自己想要注册的回调函数。当CPU通过上面两个函数访问内存时,会首先遍历mem_handler链表,如果地址落到其中之一,就调用里面的注册函数。
当然我上面提到的访问地址都已经是物理地址,而从软件指令使用的逻辑地址要经过逻辑地址->线性地址->物理地址,之间的转换,也就是CPU开启,分段、分页功能。主要的实现代码是在:CPU/page.cpp中,其中最主要的函数是:translate_linear(bx_address laddr, unsigned pl, unsigned rw, unsigned access_type);
主要过程是,分别获取页目录项、页表项,获取物理页的地址,其中对每个表项会有特权级等安全方面的检测,同时还会涉及TLB数据结构的查询、更新等管理。其中TLB数据项的结构(cpu.h)
typedef struct {
bx_address lpf; // linear page frame
bx_phy_address ppf; // physical page frame
Bit32u accessBits; // Page Table Address for updating A & D bits
bx_hostpageaddr_t hostPageAddr;
} bx_TLB_entry;
struct {
bx_TLB_entry entry[BX_TLB_SIZE] BX_CPP_AlignN(16);
} TLB;