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

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

本文内容参考:

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

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

QEMU内存管理模型

浅谈QEMU Memory Region 与 Address Space

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

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

特此致谢!

2. QEMU虚拟机内存初始化

本回继续对于QEMU内存平坦化的核心函数 —— render_memory_region()进行深入解析。为了便于理解和回顾,再次贴出render_memory_region函数代码,在softmmu/memory.c中,如下:

/* Render a memory region into the global view.  Ranges in @view obscure
 * ranges in @mr.
 */
static void render_memory_region(FlatView *view,
                                 MemoryRegion *mr,
                                 Int128 base,
                                 AddrRange clip,
                                 bool readonly,
                                 bool nonvolatile)
{
    MemoryRegion *subregion;
    unsigned i;
    hwaddr offset_in_region;
    Int128 remain;
    Int128 now;
    FlatRange fr;
    AddrRange tmp;
 
    if (!mr->enabled) {
        return;
    }
 
    int128_addto(&base, int128_make64(mr->addr));
    readonly |= mr->readonly;
    nonvolatile |= mr->nonvolatile;
 
    tmp = addrrange_make(base, mr->size);
 
    if (!addrrange_intersects(tmp, clip)) {
        return;
    }
 
    clip = addrrange_intersection(tmp, clip);
 
    if (mr->alias) {
        int128_subfrom(&base, int128_make64(mr->alias->addr));
        int128_subfrom(&base, int128_make64(mr->alias_offset));
        render_memory_region(view, mr->alias, base, clip,
                             readonly, nonvolatile);
        return;
    }
 
    /* Render subregions in priority order. */
    QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) {
        render_memory_region(view, subregion, base, clip,
                             readonly, nonvolatile);
    }
 
    if (!mr->terminates) {
        return;
    }
 
    offset_in_region = int128_get64(int128_sub(clip.start, base));
    base = clip.start;
    remain = clip.size;
 
    fr.mr = mr;
    fr.dirty_log_mask = memory_region_get_dirty_log_mask(mr);
    fr.romd_mode = mr->romd_mode;
    fr.readonly = readonly;
    fr.nonvolatile = nonvolatile;
 
    /* Render the region itself into any gaps left by the current view. */
    for (i = 0; i < view->nr && int128_nz(remain); ++i) {
        if (int128_ge(base, addrrange_end(view->ranges[i].addr))) {
            continue;
        }
        if (int128_lt(base, view->ranges[i].addr.start)) {
            now = int128_min(remain,
                             int128_sub(view->ranges[i].addr.start, base));
            fr.offset_in_region = offset_in_region;
            fr.addr = addrrange_make(base, now);
            flatview_insert(view, i, &fr);
            ++i;
            int128_addto(&base, now);
            offset_in_region += int128_get64(now);
            int128_subfrom(&remain, now);
        }
        now = int128_sub(int128_min(int128_add(base, remain),
                                    addrrange_end(view->ranges[i].addr)),
                         base);
        int128_addto(&base, now);
        offset_in_region += int128_get64(now);
        int128_subfrom(&remain, now);
    }
    if (int128_nz(remain)) {
        fr.offset_in_region = offset_in_region;
        fr.addr = addrrange_make(base, remain);
        flatview_insert(view, i, &fr);
    }
}

上一回讲到了以下代码片段:

    /* Render the region itself into any gaps left by the current view. */
    for (i = 0; i < view->nr && int128_nz(remain); ++i) {
        ……
        now = int128_sub(int128_min(int128_add(base, remain),
                                    addrrange_end(view->ranges[i].addr)),
                         base);
        int128_addto(&base, now);
        offset_in_region += int128_get64(now);
        int128_subfrom(&remain, now);
    }

这段代码用来越过fr2。当然也会存在其它情况,比如mr2尾部落在了fr2中,因此这里通过一个int128_min函数来判断。

        now = int128_sub(int128_min(int128_add(base, remain),
                                    addrrange_end(view->ranges[i].addr)),
                         base);

此时,第一个深色区域就插入完了。其base为fr2的结束地址;offset_in_region为fr2的长度与左边深色部分的和;remain为右侧深色部分的长度。

接下来就该是下一轮循环了。在下一轮循环中,把右侧深色区域所构成的FlatRange插入到view中。

以此类推,最终render_memory_region函数将返回,返回的时候,view中已经填满了以mr1为根的FlatRange。

至此,render_memory_region函数就解析完了。回顾一下其整体功能:

render_memory_region函数返回之后,回到generate_memory_topology函数中(softmmu/memory.c中)。

/* 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;
}

generate_memory_topology函数接着调用flatview_simplify函数。该函数在softmmu/memory.c中,代码如下:

/* Attempt to simplify a view by merging adjacent ranges */
static void flatview_simplify(FlatView *view)
{
    unsigned i, j, k;

    i = 0;
    while (i < view->nr) {
        j = i + 1;
        while (j < view->nr
               && can_merge(&view->ranges[j-1], &view->ranges[j])) {
            int128_addto(&view->ranges[i].addr.size, view->ranges[j].addr.size);
            ++j;
        }
        ++i;
        for (k = i; k < j; k++) {
            memory_region_unref(view->ranges[k].mr);
        }
        memmove(&view->ranges[i], &view->ranges[j],
                (view->nr - j) * sizeof(view->ranges[j]));
        view->nr -= j - i;
    }
}

generate_memory_topology函数中的can_merge函数在同文件中(就在上边),代码如下:

static bool can_merge(FlatRange *r1, FlatRange *r2)
{
    return int128_eq(addrrange_end(r1->addr), r2->addr.start)
        && r1->mr == r2->mr
        && int128_eq(int128_add(int128_make64(r1->offset_in_region),
                                r1->addr.size),
                     int128_make64(r2->offset_in_region))
        && r1->dirty_log_mask == r2->dirty_log_mask
        && r1->romd_mode == r2->romd_mode
        && r1->readonly == r2->readonly
        && r1->nonvolatile == r2->nonvolatile;
}

用来判断两个FlatRange是否可以合并。如果两个FlatRange是紧邻的,并且各种属性皆相同,则将这两个FlatRange是可以合并的,就将这两者合并起来。

再回到调用generate_memory_topology函数的address_space_update_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);
}

每一次虚拟机在进行内存提交时,在address_space_update_topology函数中都会调用generate_memory_topology函数,生成当前虚拟机内存的低平坦表示。

qemu_init()

---> qemu_create_machine()

---> cpu_exec_init_all()

---> memory_map_init()

---> address_space_init()

---> address_space_update_topology()

欲知后事如何,且看下回分解。