快乐虾

http://blog.csdn.net/lights_joy/

lights@hb165.com

本文适用于

QEMU-0.10.5

VS2008

欢迎转载,但请保留作者信息

在PC机中,由于早期版本的系统资源限制,其物理内存被分为多个不同的区域,并一直延续至今,那么QEMU是如何对这种静态内存布局进行模拟的呢?

1.1    整体内存分配

虽然PC机的物理内存被人为地分为多个不同的区域,但是在物理结构上它们仍然是连续的,因此qemu直接从宿主机中分配了一块内存:

int main(int argc, char **argv, char **envp)

{

…………………….

/* init the memory */

phys_ram_size = machine->ram_require & ~RAMSIZE_FIXED;

if (machine->ram_require & RAMSIZE_FIXED) {

if (ram_size > 0) {

if (ram_size < phys_ram_size) {

fprintf(stderr, "Machine `%s' requires %llu bytes of memory/n",

machine->name, (unsigned long long) phys_ram_size);

exit(-1);

}

phys_ram_size = ram_size;

} else

ram_size = phys_ram_size;

} else {

if (ram_size == 0)

ram_size = DEFAULT_RAM_SIZE * 1024 * 1024;

phys_ram_size += ram_size;

}

phys_ram_base = qemu_vmalloc(phys_ram_size);

if (!phys_ram_base) {

fprintf(stderr, "Could not allocate physical memory/n");

exit(1);

}

………………………….

return 0;

}

在这一段代码里面,ram_size变量的值可以通过“-m megs”参数指定,如果没指定则取默认值DEFAULT_RAM_SIZE,即:

#define DEFAULT_RAM_SIZE 128

但总共分配的内存并不只这些,还要加上machine->ram_require的大小,这个值来自于预定义的常量,对于pc模拟而言就是:

QEMUMachine pc_machine = {

/*.name =*/ "pc",

/*.desc =*/ "Standard PC",

/*.init =*/ pc_init_pci,

/*.ram_require =*/ VGA_RAM_SIZE + PC_MAX_BIOS_SIZE,

/*.nodisk_ok =*/ 0,

/*.use_scsi =*/ 0,

/*.max_cpus =*/ 255,

/*.next =*/ NULL

};

也就是说,总共分配的内存还要加上VGA_RAM_SIZE 和 PC_MAX_BIOS_SIZE:

#define VGA_RAM_SIZE (8192 * 1024)

#define PC_MAX_BIOS_SIZE (4 * 1024 * 1024)

总共12M。

在分配了内存后,将其指针保存在phys_ram_base这一全局变量中,猜测以后虚拟机访问SDRAM的操作都将访问此内存块。

1.2    内存块的再分配

如果要从前面分配的大内存块中取一小块,则必须使用qemu_ram_alloc函数:

/* XXX: better than nothing */

ram_addr_t qemu_ram_alloc(ram_addr_t size)

{

ram_addr_t addr;

if ((phys_ram_alloc_offset + size) > phys_ram_size) {

fprintf(stderr, "Not enough memory (requested_size = %" PRIu64 ", max memory = %" PRIu64 ")/n",

(uint64_t)size, (uint64_t)phys_ram_size);

abort();

}

addr = phys_ram_alloc_offset;

phys_ram_alloc_offset = TARGET_PAGE_ALIGN(phys_ram_alloc_offset + size);

if (kvm_enabled())

kvm_setup_guest_memory(phys_ram_base + addr, size);

return addr;

}

从这个函数可以看出,它使用了按顺序从低到高分配这种很简单的手段,用phys_ram_alloc_offset这一个全局变量记录当前已经分配了多少内存。

需要注意的是,这个函数最后返回的也是一个偏移量,而不是宿主机上的实际内存地址。

1.3    内存块管理

对于使用qemu_ram_alloc分配出来的内存块,通常还需要调用cpu_register_physical_memory进行注册:

static inline void cpu_register_physical_memory(target_phys_addr_t start_addr,

ram_addr_t size,

ram_addr_t phys_offset)

{

cpu_register_physical_memory_offset(start_addr, size, phys_offset, 0);

}

/* register physical memory. 'size' must be a multiple of the target

page size. If (phys_offset & ~TARGET_PAGE_MASK) != 0, then it is an

io memory page.  The address used when calling the IO function is

the offset from the start of the region, plus region_offset.  Both

start_region and regon_offset are rounded down to a page boundary

before calculating this offset.  This should not be a problem unless

the low bits of start_addr and region_offset differ.  */

void cpu_register_physical_memory_offset(target_phys_addr_t start_addr,

ram_addr_t size,

ram_addr_t phys_offset,

ram_addr_t region_offset)

{

……………..

region_offset &= TARGET_PAGE_MASK;

size = (size + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK;

end_addr = start_addr + (target_phys_addr_t)size;

for(addr = start_addr; addr != end_addr; addr += TARGET_PAGE_SIZE) {

p = phys_page_find(addr >> TARGET_PAGE_BITS);

if (p && p->phys_offset != IO_MEM_UNASSIGNED) {

………………

} else {

p = phys_page_find_alloc(addr >> TARGET_PAGE_BITS, 1);

p->phys_offset = phys_offset;

p->region_offset = region_offset;

if ((phys_offset & ~TARGET_PAGE_MASK) <= IO_MEM_ROM ||

(phys_offset & IO_MEM_ROMD)) {

phys_offset += TARGET_PAGE_SIZE;

} else {

………..

}

}

region_offset += TARGET_PAGE_SIZE;

}

…………….

}

从这段代码可以猜测到,QEMU对每一个注册进来的内存块都进行了分页,每一个页面大小为4K,且用一个结构体对这些页进行描述:

typedef struct PhysPageDesc {

/* offset in host memory of the page + io_index in the low bits */

ram_addr_t phys_offset;

ram_addr_t region_offset;

} PhysPageDesc;

然后采用某种机制对此结构体的变量进行管理。在这个结构体里的phys_offset指出这个页面的实际内容存放的位置,通过这个偏移量和phys_ram_base可以访问到这个页面的实际内容,也是通过这个手段实现了对bios内容的映射。而region_offset则指出这个内存页在其所属的内存块中的偏移量,其数值为4K的整数倍。

1.4    对PC静态内存布局的模拟

在QEMU启动对X86结构的模拟时,会调用一个叫pc_init1的函数:

/* PC hardware initialisation */

static void pc_init1(ram_addr_t ram_size, int vga_ram_size,

const char *boot_device,

const char *kernel_filename, const char *kernel_cmdline,

const char *initrd_filename,

int pci_enabled, const char *cpu_model)

{

…………………..

/* allocate RAM */

ram_addr = qemu_ram_alloc(0xa0000);

cpu_register_physical_memory(0, 0xa0000, ram_addr);

/* Allocate, even though we won't register, so we don't break the

* phys_ram_base + PA assumption. This range includes vga (0xa0000 - 0xc0000),

* and some bios areas, which will be registered later

*/

ram_addr = qemu_ram_alloc(0x100000 - 0xa0000);

ram_addr = qemu_ram_alloc(below_4g_mem_size - 0x100000);

cpu_register_physical_memory(0x100000,

below_4g_mem_size - 0x100000,

ram_addr);

………………….

/* allocate VGA RAM */

vga_ram_addr = qemu_ram_alloc(vga_ram_size);

/* BIOS load */

if (bios_name == NULL)

bios_name = BIOS_FILENAME;

snprintf(buf, sizeof(buf), "%s/%s", bios_dir, bios_name);

bios_size = get_image_size(buf);

if (bios_size <= 0 ||

(bios_size % 65536) != 0) {

goto bios_error;

}

bios_offset = qemu_ram_alloc(bios_size);

ret = load_image(buf, phys_ram_base + bios_offset);

if (ret != bios_size) {

bios_error:

fprintf(stderr, "qemu: could not load PC BIOS '%s'/n", buf);

exit(1);

}

if (cirrus_vga_enabled || std_vga_enabled || vmsvga_enabled) {

/* VGA BIOS load */

if (cirrus_vga_enabled) {

snprintf(buf, sizeof(buf), "%s/%s", bios_dir, VGABIOS_CIRRUS_FILENAME);

} else {

snprintf(buf, sizeof(buf), "%s/%s", bios_dir, VGABIOS_FILENAME);

}

vga_bios_size = get_image_size(buf);

if (vga_bios_size <= 0 || vga_bios_size > 65536)

goto vga_bios_error;

vga_bios_offset = qemu_ram_alloc(65536);

ret = load_image(buf, phys_ram_base + vga_bios_offset);

if (ret != vga_bios_size) {

vga_bios_error:

fprintf(stderr, "qemu: could not load VGA BIOS '%s'/n", buf);

exit(1);

}

/* setup basic memory access */

cpu_register_physical_memory(0xc0000, 0x10000,

vga_bios_offset | IO_MEM_ROM);

}

/* map the last 128KB of the BIOS in ISA space */

isa_bios_size = bios_size;

if (isa_bios_size > (128 * 1024))

isa_bios_size = 128 * 1024;

cpu_register_physical_memory(0x100000 - isa_bios_size,

isa_bios_size,

(bios_offset + bios_size - isa_bios_size) | IO_MEM_ROM);

………………………..

/* map all the bios at the top of memory */

cpu_register_physical_memory((uint32_t)(-bios_size),

bios_size, bios_offset | IO_MEM_ROM);

………………………

}

这段代码按从低到高的顺序依次注册了几个内存块:

l         常规内存(Conventional Memory)系统内存的第一个640 KB就是著名的常规内存。它是标准DOS程序、DOS驱动程序、常驻内存程序等可用的区域,它们统统都被放置在00000h~9FFFFh之间。

l         上位内存区(Upper Memory Area)系统内存的第一个1M内存顶端的384 KB(1024 KB - 640 KB)就是UMA,它紧随在常规内存之后。也就是说,第一个1M内存被分成640KB常规内存和384KB的UMA。这个区域是系统保留区域,用户程序不能使用它。它一部分被系统设备(CGA、VGA等)使用,另外一部分被用做ROM shadowing和Drivers。UMA使用内存区域A0000h~FFFFFh。

l         扩展内存(Extended Memory)从0x100000到系统物理内存的最大值之间的区域都属于扩展内存。当一个OS运行在Protected Mode时,它可以被访问,而在Real Mode下,则无法被访问(除非通过某些Hacker方法)。

本来扩展内存的第一个64K可以独立出来称之为HMA,但是从上面的代码可以看到,QEMU并没有将之单独列出来。

紧接着要模拟的物理内存之后,QEMU分配了8M的显存。

在显存之后,分配了一块空间给bios,而这段空间的内容则直接来自于bios.bin这一文件,QEMU提供的bios.bin大小为128K。

在bios之后,分配了64K的空间给vga bios,而这段的内容则来自于vgabios-cirrus.bin文件。

参考资料

winqemu代码的使用(2009-7-10)

http://blog.csdn.net/lights_joy/article/details/4354238

最新文章

  1. 使用HTML5技术控制电脑或手机上的摄像头
  2. iOS开发-生成随机数
  3. 组合(composition)与继承(inheritance)
  4. log4j的简单应用(转载)
  5. ibatis返回结果映射到HashMap时,列名无效的问题
  6. ### Caffe
  7. cisco通过控制口或者通过远程配置交换机
  8. php流行笔试题及答案
  9. 高放的c++学习笔记之关联容器
  10. MySQL索引和锁
  11. linux 克隆:device eth0 does not seem to be present,delaying initialization
  12. 14.5.7 Storing InnoDB Undo Logs in Separate Tablespaces 存储InnoDB Undo logs 到单独的表空间
  13. 如何打包静态库.framework文件 iOS
  14. Flask從入門到入土(一)——程序的基本結構
  15. Flex和Servlet结合上传文件报错(一)
  16. k8s网络之Flannel网络
  17. OC学习2——C语言特性之函数
  18. Shell高级编程学习笔记(基础篇)
  19. 5-05. QQ帐户的申请与登陆(25)(map运用)(ZJU_PAT)
  20. 20个令人惊叹的音乐应用程序UI,值得收藏

热门文章

  1. 处理通过&lt;input type=&quot;file&quot;&gt;的Post 请求
  2. 关于Set Nocount ON的性能 |c#调用存储过程的返回值总是-1
  3. JFrame画图基础和事件监听
  4. hsql使用架构包启动数据库
  5. java_重写与重载的区别
  6. python glob标准库基础学习
  7. web项目跨域访问
  8. XSS完全解决方案
  9. 使用openCV的静态库编译
  10. JMeter创建FTP测试