转载: QEMU深入浅出: guest物理内存管理

♀尐吖头ヾ 提交于 2019-12-17 06:48:27

作  者:Stefan Hajnoczi

领  域:Open source and virtualization

 

适宜读者:对虚拟化有一定了解

背景知识:KVM,Qemu

 

前言:

众所周知,内存是计算机系统的一个关键组成部分。使用Qemu-kvm方式创建虚拟机时,guest物理内存是由几个不同层面共同管理的。本篇基于Qemu2.5的代码对管理guest物理内存的这几个重要层面进行概括性的解释,为读者深究Qemu源代码奠定了理论基础。

 

需要注意的是本篇并未涉及guest虚拟内存方面的内容,后续会对其介绍。毕竟使用KVM虚拟化技术的一个重要前提是支持guest与host之间物理内存的转换,并未涉及Qemu中软件模拟的内存管理(即guest虚拟地址与guest物理地址之间的转换)。

 

一、Guest RAM的配置参数

    Qemu命令行选项“-m [size=]megs[,slots=n,maxmem=size]”分别定义了guest物理内存的初始化值,内存条(如DIMM)的可用插槽数以及可支持的guest物理内存最大值。

 

    有了slot与maxmem这两个参数的定义,在Qemu模拟DIMM热插拔的过程中,guest操作系统像host一样,能够监测到内存块的添加或移除。比如向guest中热插拔一块DIMM内存条,就像是在真实物理机上进行的。再者,guest内存热插拔的操作单位并不是字节,而是插入的DIMM内存条数目。

 

二、可热插拔的guest内存

    Qemu代码中的“pc-dimm”设备(参考Qemu源码hw/mem/pc-dimm.c文件中Typeinfo pc_dimm_info的定义)用来模拟DIMM内存条。代码中“pc-dimm”设备的创建相当于逻辑上热插拔了一块内存(参考代码type_register_static(&pc_dimm_info))。尽管该设备名字包含的“pc”容易让人误解,但ppc和s390机器仍沿用了该名字。

 

    旁注:上文提到的guest的初始化内存是不能用“pc-dimm”设备创建的,也无法将其热移除。

 

在QOM(Qemu Object Model)模型中,“pc-dimm”对象(参考代码include/hw/mem/pc-dimm.h文件中的PCDIMMDevice结构体)中并没有定义表示其对应物理内存的变量,但定义了一个指针用来指向其对应的“memory-backend”对象。

 

三、Memory backends

设备“memory-backend”(参考Qemu源码backends/hostmem.c文件中TypeInfo host_memory_backend_info的定义)描述的是支撑guest物理内存的host上真实的内存资源。这些内存既可以是匿名映射的内存(参考backends/hostmem-ram.c中TypeInfo ram_backend_info的定义),也可以是文件映射的内存(参考backends/hostmem-file.c中TypeInfo file_backend_info的定义)。文件映射这种方式允许Linux在host宿主机上使用大页分配的内存能够映射到guest物理内存,同时实现了共享内存,允许host的其他应用程序访问guest物理内存。

 

“pc-dimm”对象和“memory-backend”对象(参考include/sysemu/hostmem.h文件中的HostMemoryBackend结构体)作为Qemu中用户可见的guest物理内存,可以通过Qemu命令行或者QMP(Qemu Monitor Protocol)对其进行管理。然而这只是冰山一角,下面继续介绍用户不可见的guest物理内存的管理。

 

    图3-01引出下文要介绍的内存管理对象:

image图3-01

四、RAM blocks和ram_addr_t地址空间

“memory-backend”表示的host上真实内存资源由RAMBlock调用exec.c文件中的qemu_ram_alloc()函数,最终通过mmap映射到host上的一块虚拟内存。RAMBlock结构体中的uint8_t *host指向host上虚拟内存的起始值,ram_addr_t offset表示当前RAMBlock相对于RAMList(描述host虚拟内存的全局链表)的偏移量。也就是说ram_addr_t offset位于一个全局命名空间中,可以通过此offset偏移量定位某个RAMBlock。

 

    然而ram_addr_t命名空间并不等同于guest物理内存空间,它仅表示所有RAMBlock集合构成的一个地址空间。举个例子,guest物理地址0x100001000可能并不对应于ram_addr_t命名空间中的0x100001000地址,因为ram_addr_t命名空间不包含guest物理内存区域中用于预留和I/O内存映射的这些部分。此外ram_addr_t offset的值取决于RAMBlock被创建的顺序,而在guest物理内存空间中每块内存都有其固定的地址。

 

    所有的RAMBlock以链表节点的形式存放在全局链表RAMList(参考include/exec/ram_addr.h文件中的struct RAMList)中,即ram_list。结构体ram_list中记录了所有RAMBlock以及“脏”内存bitmap的信息。

 

五、“脏”内存页面的追踪

    当guest CPU或通过设备DMA向guest RAM中存储数据时,需要通知以下特性:

  1. 动态迁移特性会一直跟踪“脏”内存页,在迁移过程中一旦发现这些内存页发生变化就会重新向目的主机发送这些“脏”页面。
  2. Qemu中的动态翻译器TCG(Tiny Code Generator)会一直追踪自调整的代码,当上游指令发生变化时对其重新编译。
  3. 显卡仿真会一直追踪视频相关的“脏”内存,并重新绘制扫描线。

 

以上每种特性在结构体ram_list的成员变量DirtyMemoryBlocks *dirty_memory[DIRTY_MEMORY_NUM]中都有其对应的bitmap,为以上特性独立的开启或关闭“脏”页面跟踪机制。

 

六、Address spaces地址空间

    所有CPU体系架构都有其内存空间,有一些架构还有单独的I/O地址空间。I/O地址空间由AddressSpace结构体描述,一个AddressSpace对应一棵MemoryRegion树(其对应关系在include/exec/memory.h文件的AddressSpace结构体中定义:mr = as->root)。

 

    结构体MemoryRegion是联系guest物理地址空间和描述真实内存的RAMBlocks之间的桥梁。每个MemoryRegion结构体中定义了ram_addr_t offset成员指向其对应RAMBlock中的offset成员变量[1],而在RAMBlock结构体中则定义了struct MemoryRegion *mr指向对应的MemoryRegion。

 

要注意MemoryRegion不仅用来描述RAM,还可以用来描述I/O内存,在访问I/O内存时调用read/write回调函数。如guest CPU在访问硬件设备寄存器时通过查找其对应MemoryRegion结构体中的信息去访问指定的模拟设备。

 

函数address_space_rw()访问MemoryRegion并对该MR描述的内存执行load/store操作。RAM类型的MemoryRegion描述的内存可通过访问RAMBlock中的guest物理内存来获取。Guest物理内存空间定义了一个全局变量AddressSpace address_space_memory,用来表示跟RAM相关的内存。

 

七、总结

Guest物理内存的管理是由几个不同层面共同作用的:“pc-dimm”和“memory-backend”描述了用户可见的guest物理内存(分别代表DIMM内存条与host真实的内存资源);RAMBlock描述了通过mmap映射的host虚拟内存块;AddressSpace和MemoryRegion则记录了guest物理内存与host虚拟内存之间的映射。

 

注释[1]:在Qemu2.6的最新代码中,去除了ram_addr_t offset成员的定义,改为定义RAMBlock *ram_block成员,指向其对应的RAMBlock。

 

参考资料:

http://blog.vmsplice.net/2016/01/qemu-internals-how-guest-physical-ram.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!