浅谈QEMU Memory Region 与 Address Space
文章目录
1. Memory Region
1.1 Memory Region & Address Space & Device
参考:http://blog.vmsplice.net/2016/01/qemu-internals-how-guest-physical-ram.html
-
三者的关系可以用下图简单表示:
-
从下往上看,首先每一个Memory Region对象都包含了RAMBlock,RAMBlock是QEMU真正向Host alloc申请空间的数据结构,它的大小可以指定;而Memory Region是从RAMBlock进行了抽象,有助于Device操作内存空间。
- QEMU维护了RAMList,每申请一个RAMBlock(Memory Region)添加到List中
-
Address Space包含了一系列Memory Region,在当前上下文空间中,通过地址空间对当前一系列存储对象进行一个统一抽象,很抽象举个例子
- 比如当前是CPU正在运行的状态,就有了一个Address Space名为CPU-system,此时的寻址空间,高位是操作系统内核空间,中间有0x40000000~0x7FFFFFFF共4G的地址是用于RAM内存空间,还有一些地址用于I/O
- 后来需要I/O,进入I/O状态后,又有了一个Address Space名为I/O,此时的所有地址都是I/O地址
-
对于memory-backend,可以不用理解,只需要知道大多数device都需要和存储交互,操作的对象是Memory Region即可
-
Memory Region、RamBlock的对应关系可以参考上图
1.1.1 PCI Address Space
-
PCI是如今最常用的总线协议,QEMU虚拟机默认的总线协议也是PCI。需要特别指出的是
- PCI地址空间是可以开机配置的,也就是说此时我还在用0x45F23F32访问内存地址,经过配置后,再次启动,这个地址可能是属于某个PCI Device的IO地址
- 由于64位地址空间十分宽阔,所以PCI地址空间一般可以认为只有一个,将所有的设备都涵盖在64位地址空间内
-
如上图的例子,灰色部分是一个PCI的地址空间,有两个Memory Region分别是两个pci-hole,大小为128个字节,中间有个1G的RAM内存
-
我们可以对PCI地址空间进行配置
扩大RAM内存的容量
1.2 Memory Region 类型
- RAM类型:
- 内存Memory和Cache就是RAM类型,创建接口为:
- memory_region_init_ram()
- 特殊的:memory_region_init_resizeable_ram(), memory_region_init_ram_from_file(), or memory_region_init_ram_ptr()
- 内存Memory和Cache就是RAM类型,创建接口为:
- MMIO类型:
- 用于host callbacks创建的guest memory,如每次read or write操作会导致host的一个callback,可以通过memory_region_init_io()创建
- ROM类型
- read-only-memory,主要用于闪存,比如存OS的加载器,创建接口:
- memory_region_init_rom()
- read-only-memory,主要用于闪存,比如存OS的加载器,创建接口:
- ROM Device类型:
- 像RAM一样read,同时write会调用host callback的device,创建接口:
- memory_region_init_rom_device()
- 像RAM一样read,同时write会调用host callback的device,创建接口:
- IOMMU类型:
- 有IOMMU职责的region,即能将地址直接翻译到目标的memory region,只能用于IOMMU device,创建接口:
- memory_region_init_iommu()
- 有IOMMU职责的region,即能将地址直接翻译到目标的memory region,只能用于IOMMU device,创建接口:
- container类型:
- 顾名思义,包含了其他的mr,记录每个mr的offset。container可以管理多个mr为一个单位,十分有用。比如,一个PCI BAR可能包含了一个RAM region和一个MMIO region
- 创建container mr使用纯净的init方式:
- memory_region_init()
- alias类型:
- 一个region的子集。允许将一个region分解成不连续的子region,创建接口:
- memory_region_add_subregion(),用于将一个已存在的region加入到某个container中
- memory_region_init_alias(),用于创建一个新的region
- 一个region的子集。允许将一个region分解成不连续的子region,创建接口:
- reservation region类型:
- 用于debug,在enable-kvm之后使用
- memory_region_init_io()
- 用于debug,在enable-kvm之后使用
- qemu mr支持向所有的非container中add一个subregion,比如MMIO,RAM,ROM region,此时这些Region也表现为container,除非规定了这个region的所有地址都由该region自己处理
- 但qemu不支持向alias类型的region中加入subregion
1.3 Memory Region 生命周期
- 首先一个mr的诞生从memory_region_init*()函数开始,并且mr属于一个已存在的对象,比如vcpu,说明这个mr是vcpu的一部分,称这个vcpu对象是此mr的owner或者parent
- QEMU规定只要mr还对guest可见,owner就不会死亡
- 被创建后的mr,可以被加入到一个address space或container,或者被移除
- 接口分别为:memory_region_add_subregion()和memory_region_del_subregion()
- mr被创建后,职责不是一成不变的,可以修改,发生在mr被置为visible的时候赋予
- 当owner死亡后,mr会被自动析构清除
- mr也可以手动清除
- 接口为object_unparent()
注意:尽量不要在一个device生命周期中动态的创建使用mr,即使这样做了,也要注意显式free掉
1.4 Memory Region Overlapping and Priority
-
通常,一个regions会被解析成一个唯一确定的目标地址,并不会重叠(overlapping),但一些场景下mr会重叠。即,一个mr对应了多个目标地址,在一次调用中使用哪个目标地址,取决于这些目标地址的优先级(priority),即优先级高的被返回。
- 对应的接口:memory_region_add_subregion_overlap(),允许一个region和同一个容器中的其他region重叠,并且声明优先级
-
看下面的例子
-
container A从0开始,size为0x8000,A有2个subregions:B和C,B是一个从0x2000开始,大小为0x4000的容器,优先级为2;C是一个从0开始,大小为0x6000的MMIO region,优先级为1。B容器中有2个subregions:D和E
-
那么C范围的地址可以看成:
-
[CCCCCCCCCCCC][DDDDD][CCCCC][EEEEE][CCCCC]
-
因为B的优先级高于C,所以用的是B容器的subregions,但是B并不是连续存在的,有空洞(holes),此时QEMU默认用上一级填满,所以用CCCCC填满
注意:priority对于container是local的,即两个容器内的两个region比较priority是没意义的
-
-
如果B的空洞被赋予值了,比如B提供自己的MMIO操作,那么结果就是
-
[CCCCCCCCCCCC][DDDDD][BBBBB][EEEEE][BBBBB]
-
1.5 Visibility
- 当你获得一个address时,怎么确定这个地址的值
- 首先得根据地址空间确定当前的地址映射
- 再查找address上的值
1.6 FlatView
- 通常一个系统的地址空间有多个,而且mr还是重叠的,那么如何快速根据一个地址就得到它映射的位置
- 正常的做法是,先确定当前地址空间的visibility,然后在计算该地址的映射
- QEMU设计了FlatView Buffer,用于保存一个完整的地址空间映射,这样下次再用到这个地址空间的时候就可以直接获得
- 接口:generate_memory_topology()
思考
-
为什么memory region可以重叠,为什么需要alias的概念
参考:https://martins3.github.io/qemu/memory.html#memoryregion
- 很简单,这样可以让QEMU代码变得简单
- 如果不能重叠,那么一个模块要拆改那就Memory Region的时候,就要考虑其他模块的Memory Region的大小和边界
- 使用alias可以将一个Memory Region的一部分放到另外一个mr上,因为对于一个地址空间来说,并不是按照连续的方式给每一个类型的memory划分区域的,而是离散的。比如一个pc.ram,创建的时候就有ram-below-4g和ram-above-4g,分别位于地址空间的尾部和头部,如果没有alias,就需要创建两个pc.ram,就需要mmap两次,之后的操作也需要两次
- 很简单,这样可以让QEMU代码变得简单