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

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

本文内容参考:

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

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

QEMU内存管理模型

浅谈QEMU Memory Region 与 Address Space

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

QEMU内存分析(一):内存虚拟化关键结构体 - Edver - 博客园

特此致谢!

2. QEMU虚拟机内存初始化

上一回沿着主线走到了memory_map_init函数,重点讲解了其中的系统内存区域system_memory相关代码中的memory_region_init函数,本回解析address_space_init函数。为了便于理解和回顾,再次贴出memory_map_init函数代码,其在softmmu/physmem.c中,如下:

static void memory_map_init(void)
{
    system_memory = g_malloc(sizeof(*system_memory));
 
    memory_region_init(system_memory, NULL, "system", UINT64_MAX);
    address_space_init(&address_space_memory, system_memory, "memory");
 
    system_io = g_malloc(sizeof(*system_io));
    memory_region_init_io(system_io, NULL, &unassigned_io_ops, NULL, "io",
                          65536);
    address_space_init(&address_space_io, system_io, "I/O");
}

address_space_init函数也在softmmu/memory.c中,代码如下:

void address_space_init(AddressSpace *as, MemoryRegion *root, const char *name)
{
    memory_region_ref(root);
    as->root = root;
    as->current_map = NULL;
    as->ioeventfd_nb = 0;
    as->ioeventfds = NULL;
    QTAILQ_INIT(&as->listeners);
    QTAILQ_INSERT_TAIL(&address_spaces, as, address_spaces_link);
    as->name = g_strdup(name ? name : "anonymous");
    address_space_update_topology(as);
    address_space_update_ioeventfds(as);
}

先来看一下address_space_init函数参数对应的实参:

    address_space_init(&address_space_memory, system_memory, "memory");
  • AddressSpace *as

对应的实参为&address_space_memory。address_space_memory是同文件(softmmu/physmem.c)中定义的全局变量:

AddressSpace address_space_io;
AddressSpace address_space_memory;
  • MemoryRegion *root

对应的实参为system_memory。system_memory就是在上边创建(分配)并初始化的。

    system_memory = g_malloc(sizeof(*system_memory));
 
    memory_region_init(system_memory, NULL, "system", UINT64_MAX);
    address_space_init(&address_space_memory, system_memory, "memory");
  • const char *name

对应的实参就是"memory"。

函数参数弄清楚之后,开始看address_space_init函数代码。

首先调用memory_region_ref函数,代码片段如下:

    memory_region_ref(root);

memory_region_ref函数也在softmmu/memory.c中,代码如下:

void memory_region_ref(MemoryRegion *mr)
{
    /* MMIO callbacks most likely will access data that belongs
     * to the owner, hence the need to ref/unref the owner whenever
     * the memory region is in use.
     *
     * The memory region is a child of its owner.  As long as the
     * owner doesn't call unparent itself on the memory region,
     * ref-ing the owner will also keep the memory region alive.
     * Memory regions without an owner are supposed to never go away;
     * we do not ref/unref them because it slows down DMA sensibly.
     */
    if (mr && mr->owner) {
        object_ref(mr->owner);
    }
}

函数中的注释已经讲得很清楚了:

MMIO回调很可能会访问属于owner的数据,因此需要在使用内存区域时引用/取消(ref/unref)引用owner。

内存区域是其owner的子区域。只要owner没有在内存区域上调用unparent本身,引用所有者也会使内存区域保持活动状态。
没有own的记忆区域应该永远不会消失;我们不引用/取消引用它们,因为这会明显减慢DMA的速度。

接下来对于AddressSpace *as所指向的&address_space_memory、即address_space_memory的各成员进行初始化。代码片段如下:

    as->root = root;
    as->current_map = NULL;
    as->ioeventfd_nb = 0;
    as->ioeventfds = NULL;
    QTAILQ_INIT(&as->listeners);
    QTAILQ_INSERT_TAIL(&address_spaces, as, address_spaces_link);
    as->name = g_strdup(name ? name : "anonymous");

再来回顾一下AddressSpace结构的定义,在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;
};
  • address_space_memory.root为system_memory,这样就把AddressSpace和MemoryRegion串联起来了。
  • address_space_memory.current_map为NULL。
  • address_space_memory.ioeventfd_nb为0。
  • address_space_memory.ioeventfds为NULL。
  • address_space_memory.name为"memory"。

余下的成员都是与QTAILQ相关的了,放在下一回解析。