快乐虾

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

Qemu对x86静态内存布局的模拟的更多相关文章

  1. linux系统进程的内存布局

    内存管理模块是操作系统的心脏:它对应用程序和系统管理非常重要.今后的几篇文章中,我将着眼于实际的内存问题,但也不避讳其中的技术内幕.由于不少概念是通用的,所以文中大部分例子取自32位x86平台的Lin ...

  2. C语言程序内存布局

    C语言程序内存布局 如有转载,请注明出处:http://blog.csdn.net/embedded_sky/article/details/44457453 作者:super_bert@csdn 一 ...

  3. Anatomy of a Program in Memory.剖析程序的内存布局

    原文标题:Anatomy of a Program in Memory 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩文章翻译 ...

  4. C++ 中类的内存布局

    在许多笔试面试中都会涉及到sizeof 运算符的求值问题. 这类问题主要分四类: 基本数据类型,如int,bool,fload,long,long,int * 等,这一类比较简单,但要注意x86和x6 ...

  5. 重磅硬核 | 一文聊透对象在 JVM 中的内存布局,以及内存对齐和压缩指针的原理及应用

    欢迎关注公众号:bin的技术小屋 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以 ...

  6. C++ 系列:内存布局

    转载自http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html 为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内 ...

  7. C++中派生类对象的内存布局

    主要从三个方面来讲: 1 单一继承 2 多重继承 3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性. 派生类可以看作是完整的基类的Object再加上派生类自己的Objec ...

  8. C++ 多继承和虚继承的内存布局(转)

    转自:http://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中,我们 ...

  9. c++继承中的内存布局

    今天在网上看到了一篇写得非常好的文章,是有关c++类继承内存布局的.看了之后获益良多,现在转在我自己的博客里面,作为以后复习之用. ——谈VC++对象模型(美)简.格雷程化    译 译者前言 一个C ...

随机推荐

  1. Qt自定义sleep延时函数(巧妙的使用时间差,但这样似乎CPU满格,而不是沉睡)

    Qt不像VC++的win32/MFC编程那样,提供了现成的sleep函数可供调用.Qt把sleep函数封装在QThread类中.子线程可以调用sleep函数.但是如果用户想在主线程实现延时功能,该怎么 ...

  2. Git for windows GUI使用

    试用了下CSDN的Code 期间还遇到一个错误 http://blog.csdn.net/utstarm/article/details/8249853 这个新手教程写得不错 https://code ...

  3. Spring 的优秀工具类盘点第 1 部分

    文件资源操作 文件资源的操作是应用程序中常见的功能,如当上传一个文件后将其保存在特定目录下,从指定地址加载一个配置文件等等.我们一般使用 JDK 的 I/O 处理类完成这些操作,但对于一般的应用程序来 ...

  4. pager-taglib 使用说明2

    传两个值进去:1.pm.totles总记录数 2.pagesize 每页显示页数 3.<pg:param name="parentId"/>传给后台的变量值对(查询条件 ...

  5. 带中文索引的ListView 仿微信联系人列表

    因为各种原因,项目经理和产品经理把我做的东西给否定了,所以决定分享出去. 主要功能: 1 .带中文索引的ListView 2.自己定义顶部搜索视图,能够对返回button,搜索button加入事件监听 ...

  6. js中的referrer使用,返回上一页

    js完整代码: <script language="javascript"> var refer=document. referrer ;     document.g ...

  7. [Cycle.js] Making our toy DOM Driver more flexible

    Our previous toy DOM Driver is still primitive. We are only able to sends strings as the textContent ...

  8. Codeforces Round #311 (Div. 2) E - Ann and Half-Palindrome(字典树+dp)

    E. Ann and Half-Palindrome time limit per test 1.5 seconds memory limit per test 512 megabytes input ...

  9. git config配置文件 (共有三个配置文件)

    设置 git status的颜色. git config --global color.status auto 一.Git已经在你的系统中了,你会做一些事情来客户化你的Git环境.你只需要做这些设置一 ...

  10. HDU 5904 - LCIS (BestCoder Round #87)

    HDU 5904 - LCIS [ DP ]    BestCoder Round #87 题意: 给定两个序列,求它们的最长公共递增子序列的长度, 并且这个子序列的值是连续的 分析: 状态转移方程式 ...