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

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

本文内容参考:

《趣谈 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_memory_region函数很复杂。为了更好地理解该函数,在此通过举例来对于其进行解析。假设有一个MemoryRegion mr1以及虚拟机地址空间。mr1有一个子MemoryRegion mr2,虚拟机物理地址空间已经展开了两个FlatRange,分别是fr1和fr2。如下图所示:

接下来是平坦化各个子region,在上述例子中是mr2。代码片段如下:

    /* Render subregions in priority order. */
    QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) {
        render_memory_region(view, subregion, base, clip,
                             readonly, nonvolatile);
    }

又回到了这一段代码。但注意,此时调用render_memory_region函数时,clip已经属于mr2的范围了。

接着计算一个offset_in_region,为clip.start-base。代码片段如下:

    offset_in_region = int128_get64(int128_sub(clip.start, base));
    base = clip.start;
    remain = clip.size;

注意,此时base为mr2->addr(见上图),因此这个offset_in_region为马上要创建的FlatRange相对于mr2的起始位置,最开始为0。接着设置base为clip的起始位置,需要展开的大小remain为clip.size,即mr2->size。

接下来,初始化一个FlatRange,代码片段如下:

    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;

此时开始准备展开mr2了。假设mr2已经有一部分展开了(这是可能的,因为一段内存可以由多个MemoryRegion描述),所以这个时候要展开的就是下图中的深蓝色部分。

这个时候要遍历view中的所有FlatRange,如果base的值大于等于FlatRange的最后边界,也就是range对应在mr2的左边,那么不用处理。代码片段如下:

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

如果base的值小于range的起始地址,也就是上图中fr2左边深蓝色部分的情况,就需要把这个深蓝色区域展开,代码片段如下:

    /* Render the region itself into any gaps left by the current view. */
    for (i = 0; i < view->nr && int128_nz(remain); ++i) {
        ……
        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);
        }
        ……
    }

对于该段代码以及render_memory_region函数后续代码的解析,请看下回。