zoukankan      html  css  js  c++  java
  • Qemu对x86静态内存布局的模拟

    快乐虾

    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

  • 相关阅读:
    mbedtls安装与入门【转】
    SpringAop代理对象调用过程(八)
    SpringAOP理解-代理对象创建(七)
    NIO与零拷贝
    NIO实现群聊系统
    SpringAOP概述(六)
    NIO简介以及三大组件(BufferChannelSelector)基本使用
    BIO基本介绍以及使用
    Netty简介
    Spring循环依赖解决(五)
  • 原文地址:https://www.cnblogs.com/findumars/p/5733819.html
Copyright © 2011-2022 走看看