QEMU源码全解析 —— 内存虚拟化(11)

接前一篇文章: QEMU源码全解析 —— 内存虚拟化(10)

本文内容参考:

《趣谈 Linux操作系统 》 —— 刘超, 极客时间

QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社

QEMU内存管理模型

浅谈QEMU Memory Region 与 Address Space

【QEMU系统分析之实例篇(七)】-CSDN博客

特此致谢!

2. QEMU虚拟机内存初始化

上一回开始对于之前没有仔细讲过的知识进行精讲,首先是虚拟机内存平坦化,即地址空间(AddressSpace)中与树形结构(MemoryRegion)并列的“平面”结构(FlatView)两者之间的转换。

先再来回顾一下几个结构体的定义。

  • AddressSpace

AddressSpace的定义在include/qemu/typedefs.h中,如下:

typedef struct AddressSpace AddressSpace;

struct Address的定义在include/exec/memory.h中,如下:

/**
 * struct AddressSpace: describes a mapping of addresses to #MemoryRegion objects
 */
struct AddressSpace {
    /* private: */
    struct rcu_head rcu;
    char *name;
    MemoryRegion *root;

    /* Accessed via RCU.  */
    struct FlatView *current_map;

    int ioeventfd_nb;
    struct MemoryRegionIoeventfd *ioeventfds;
    QTAILQ_HEAD(, MemoryListener) listeners;
    QTAILQ_ENTRY(AddressSpace) address_spaces_link;
};

AddressSpace结构用来表示一个虚拟机或者虚拟CPU能够访问的所有物理地址。

AddressSpace中同时包含了两个QEMU内存管理框架的核心结构:MemoryRegion和FlatView。这两者配合使用,MemoryRegion是基础,用于正向管理;Flatview用于反向查找。

  • MemoryRegion

MemoryRegion的定义在include/exec/memory.h中,如下:

#define TYPE_MEMORY_REGION "memory-region"
DECLARE_INSTANCE_CHECKER(MemoryRegion, MEMORY_REGION,
                         TYPE_MEMORY_REGION)

在此不详细展开宏,而直接给出struct MemoryRegion定义,也在include/exec/memory.h中,代码如下:

/** MemoryRegion:
 *
 * A struct representing a memory region.
 */
struct MemoryRegion {
    Object parent_obj;
 
    /* private: */
 
    /* The following fields should fit in a cache line */
    bool romd_mode;
    bool ram;
    bool subpage;
    bool readonly; /* For RAM regions */
    bool nonvolatile;
    bool rom_device;
    bool flush_coalesced_mmio;
    uint8_t dirty_log_mask;
    bool is_iommu;
    RAMBlock *ram_block;
    Object *owner;
    /* owner as TYPE_DEVICE. Used for re-entrancy checks in MR access hotpath */
    DeviceState *dev;
 
    const MemoryRegionOps *ops;
    void *opaque;
    MemoryRegion *container;
    int mapped_via_alias; /* Mapped via an alias, container might be NULL */
    Int128 size;
    hwaddr addr;
    void (*destructor)(MemoryRegion *mr);
    uint64_t align;
    bool terminates;
    bool ram_device;
    bool enabled;
    bool warning_printed; /* For reservations */
    uint8_t vga_logging_count;
    MemoryRegion *alias;
    hwaddr alias_offset;
    int32_t priority;
    QTAILQ_HEAD(, MemoryRegion) subregions;
    QTAILQ_ENTRY(MemoryRegion) subregions_link;
    QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;
    const char *name;
    unsigned ioeventfd_nb;
    MemoryRegionIoeventfd *ioeventfds;
    RamDiscardManager *rdm; /* Only for RAM */
 
    /* For devices designed to perform re-entrant IO into their own IO MRs */
    bool disable_reentrancy_guard;
};

MemoryRegion表示的是虚拟机中的一段内存区域,它是内存模拟中的核心结构,整个内存的模拟都是通过MemoryRegion构成的无环图完成的。图的叶子节点是实际分配给虚拟机的物理内存或者MMIO,中间节点则表示内存总线,内存控制器是其它MemoryRegion的别名。

  • FlatView

FlatView的定义也在include/exec/memory.h中(就在struct Address定义的下边),如下:

/* Flattened global view of current active memory hierarchy.  Kept in sorted
 * order.
 */
struct FlatView {
    struct rcu_head rcu;
    unsigned ref;
    FlatRange *ranges;
    unsigned nr;
    unsigned nr_allocated;
    struct AddressSpaceDispatch *dispatch;
    MemoryRegion *root;
};

FlatView结构就是把树形内存结构变成平的内存结构。因为树形内存结构比较容易管理,而平的内存结构比较方便与内核通信以请求物理内存。虽然操作系统内核里边实际上也是用树形结构来表示内存区域的,但是在用户态向内核申请内存的时候,会按照平的、连续的模式进行申请。那么,由于QEMU也是在用户态,它也得按照平的、连续的模式进行申请,所以就要做这样一个转换。

虚拟机内存的平坦化以AddressSpace为单位,也就是以AddressSpace的根MemoryRegion为起点,将其所表示的内存拓扑的无环图结构变为平坦模式。具体的实现函数是address_space_update_topology和其中的generate_memory_topology,两个均在softmmu/memory.c中,代码如下:

static void address_space_update_topology(AddressSpace *as)
{
    MemoryRegion *physmr = memory_region_get_flatview_root(as->root);

    flatviews_init();
    if (!g_hash_table_lookup(flat_views, physmr)) {
        generate_memory_topology(physmr);
    }
    address_space_set_flatview(as);
}
/* Render a memory topology into a list of disjoint absolute ranges. */
static FlatView *generate_memory_topology(MemoryRegion *mr)
{
    int i;
    FlatView *view;

    view = flatview_new(mr);

    if (mr) {
        render_memory_region(view, mr, int128_zero(),
                             addrrange_make(int128_zero(), int128_2_64()),
                             false, false);
    }
    flatview_simplify(view);

    view->dispatch = address_space_dispatch_new(view);
    for (i = 0; i < view->nr; i++) {
        MemoryRegionSection mrs =
            section_from_flat_range(&view->ranges[i], view);
        flatview_add_to_dispatch(view, &mrs);
    }
    address_space_dispatch_compact(view->dispatch);
    g_hash_table_replace(flat_views, mr, view);

    return view;
}

从代码层面概括来讲,虚拟机内存的平坦化过程是将AddressSpace中的根MemoryRegion表示的虚拟机内存地址空间转变成一个平坦的线性地址空间。每一段线性地址空间的属性和其所属的MemoryRegion都一致,每一段线性空间与虚拟机的物理地址空间都相互关联。

虚拟机内存的平坦化 以AddressSpace为单位 ,也就是 以AddressSpace的根MemoryRegion为起点 ,将其所表示的内存拓扑的无环图变成平坦模式。

更多内容请看下回。