• 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

  • 相关阅读:
    【Azure API 管理】解决API Management添加AAD Group时遇见的 Failed to query Azure Active Directory graph due to error 错误
    【Azure 云服务】Azure Cloud Service (Extended Support) 云服务开启诊断日志插件 WAD Extension (Windows Azure Diagnostic) 无法正常工作的原因
    【Azure 应用服务】App Service For Linux 环境中,如何从App Service中获取GitHub私有库(Private Repos)的Deploy Key(RSA key)呢?
    【Azure 应用服务】App Service与Application Gateway组合使用时发生的域名跳转问题如何解决呢?
    【Azure 应用服务】App Service"访问控制/流量监控"四问
    【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
    【Azure Fabric Service】Service Fabric 遇见错误信息记录 The process/container terminated with exit code:2148734499
    【Azure Developer】使用PowerShell WhereObject方法过滤多维ArrayList时候,遇见的诡异问题 当查找结果只有一个对象时,返回结果修改了对象结构,把多维变为一维
    【Azure 事件中心】Spring Boot 集成 Event Hub(azurespringcloudstreambindereventhubs)指定Partition Key有异常消息
    Spring系列28:@Transactional事务源码分析
  • 原文地址:https://www.cnblogs.com/findumars/p/5733819.html
Copyright © 2020-2023  润新知