QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/leoufung/article/details/48781209
 关于MemoryRegion请参考我的博客《MemoryRegion模型原理,以及同FlatView模型的关系(QEMU2.0.0)》,今天主要讲一下QEMU如何生成内存拓扑。
主要涉及的函数为:FlatView *generate_memory_topology(MemoryRegion *mr),输入一个MR,将MR平展的FlatView返回 
该函数的主要目的是为一个MemoryRegion所管理的内存模型生成对应的FlatView内存拓扑模型


QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
/*
* 将MR所管理的内存展开成FlatView后返回
*/
static FlatView *generate_memory_topology(MemoryRegion *mr)
{
    FlatView *view;

     /*新分配一个FlatView并初始化*/
    view = g_new(FlatView, 1);
    flatview_init(view);

    if (mr) {
          /*
          * 将mr所管理的内存进行平坦展开为FlatView,通过view返回
          * addrrange_make(int128_zero(), int128_2_64()) --> 指定一个GUEST内存空间,起始GPA等于0,大小等于2^64,可以想象成GUEST的整个物理地址空间         
          */
        render_memory_region(view, mr, int128_zero(),
                             addrrange_make(int128_zero(), int128_2_64()), false);
    }

     /*简化FlatView,将View中的FlatRange能合并的都合并*/
    flatview_simplify(view);

    return view;
}


先看最主要的函数render_memory_region。TMD,这个函数太难描述了,先看几个图了解一个原理,大家根据原理再自己体会一下

下面口语化一点口语化的情况下
第一次进入render_memory_region时候,传了一个2^64大小的超级大的GUEST内存(clip参数代表),base代表了相对这个超大内存物理其实地址,开始为0;然后将MR代表的地址空间和这个这个超大的内存相与,得到新的clip,新的clipMR所代表的地址空间,base指向了MR所代表地址空间的起始位置

QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 

这里先说一下offset_in_region变量的作用,这个值表示的是该alias类型MR表示的起始GPA相对于分配了物理内存的实体MR起始GPA的偏移量,我们通过ram_above_4g为例来查看一下

QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
ram_above_4g是一个alias类型的MR,其源头(alias成员指向)是名为pc.ram的MR,pc.ram中真正分配的物理内存,并记录了这块物理内存的起始HVA。如上图所示base指向了GPA=0x0的位置,ram_above_4g就是本函数里的clip,二者之间产生了一个偏移量,就是offset_in_region。
这个offset_in_region通常用来计算ram_above_4g这段MR里的GPA地址在物理内存RAM上的HVA。还是以上图为例,ram_above_4g中起始GPA对应的HVA=pc.ram起始HVA+offset_in_region。依次类推ram_above_4g范围内其他GPA对应的HVA,这样就可以建立GPA到HVA的对应关系了。
细心的有人会问,直接加上偏移量不就可以了吗,为什么还要搞offset_in_region。我们前面在《MemoryRegion模型原理,以及同FlatView模型的关系(QEMU2.0.0)》里面讲过,MR是需要平展成多个FlatRange后才注册到KVM模块中的,offset_in_region就是给了这些被展开的FlatRange,而每个FlatRange在GPA地址空间上是不一定相邻的,通过offset_in_region就可以计算FlatRange其实GPA地址对以的HVA的值了。

下面我们来讲一下展开的过程,对应的函数是render_memory_region
先界定一下其实场景
QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
GUEST物理地址空间中,当前MR中已经有r1~r3,3个FlatRange已经展开了并填充了,GUEST地址空间起始GPA=0x0;黄色代表了待展开MR对应的地址空间,base指向了clip的起始GPA,remain代表了还有多长没有展开。
这里有一点需要注意,FlatView是属于AddressSpace的,AddressSpace代表了某一用途的内存,下面挂了一个MemoryRegion树。所以一个FlatView可以对应多个MemoryRegion,就可能出现图中所示的,r1已经在别的MemoryRegion中被展开,而r1范围在当前clip所管理范围之外的情况。
另外,为什么会有r2和r3存在,是因为可能现在的操作是对虚拟机插入新的内存,在更新内存拓扑,这样原来的内存就是R2和R3。

base就是clip的其实地址,remian就是clip的长度。随着不断的展开,base会向后移动,remian会不断减小
下面开始展开过程
1.  比较r1,发现base比r1的结尾大,直接跳过r1。base和remain保持不变
QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
2.  比较r2,发现base的其实地址在r2前面,需要将小于r2的部分展开到GUEST地址空间上,now代表了本次要展开的部分长度,展开后,base移动到r2之后,remian减小now长度。
QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
3. 和r2重叠的部分直接跳过,不需要展开,因为已经展开过了。本次now为跳过的r2的长度,base向后移动到now后面,remian减少now长度

QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
4. 同r2一样,处理r3,这里不在赘述

QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
5. 当所有已经存在的FlatRange都遍历完了,发现还有一部分内存clip没有被展开,那么展开这块内存到GUEST物理地址空间上
QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
到此为止,MR所代表的内存就全部展开为FlatView下的FlatRange了

原理讲完了,上代码

/* Render a memory region into the global view.  Ranges in @view obscure
* ranges in @mr.
*/
/*
* render: 致使;提出;实施;着色;以…回报
*/
/*
* 将MR中所管理的内存,在clip指定的地址空间,逐个形成FlatRange后,将所有的FlatRange加入FlatView中
*
* @view:           待形成的View
* @mr:              待展平的Mr
* @clip:            待展开的内存将被展开在clip所在的区域内,目前clip代表了整个内存
* @base:          可以理解为clip的起始地址
* @readonly:     读写属性
*/
static void render_memory_region(FlatView *view,
                                 MemoryRegion *mr,
                                 Int128 base,
                                 AddrRange clip,
                                 bool readonly)
{
    MemoryRegion *subregion;
    unsigned i;
    hwaddr offset_in_region;     /*在region对应的真实物理内存的偏移量*/
    Int128 remain;      /*待展开内存的长度*/
    Int128 now;      /*本次展开的长度或跳过的长度*/
    FlatRange fr;
    AddrRange tmp;

    if (!mr->enabled) {
        return;
    }

     /*base改为MR的起始地址*/
    int128_addto(&base, int128_make64(mr->addr));
    readonly |= mr->readonly;

     /*取得mr所表示的物理地址范围tmp*/
    tmp = addrrange_make(base, mr->size);

     /*更新clip为MR所代表的地址空间*/
    if (!addrrange_intersects(tmp, clip)) {
        return;
    }

    clip = addrrange_intersection(tmp, clip);

     /*如果是alias类型的MR,首先对原始MR进行FlatView展开*/
    if (mr->alias) {
          /*将base指向alias源MR的起始地址位置*/
        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);
        return;
    }

    /* Render subregions in priority order. */
     /* 对所有子MR递归进行FlatView展开 */
    QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) {
        render_memory_region(view, subregion, base, clip, readonly);
    }

    if (!mr->terminates) {
        return;
    }

     /*
     * 运行都这里说明MemoryRegion的子MR和aliaseMR都已经展开了
     */
     /*
     * 更新offset_in_region,offset_in_region是base相对于MR起始位置的偏移量,以后用来计算本FR对应MR的物理内存的HVA
     * 只有是alias类型的MemoryRegion,相应的offset_in_region才不为0,
     */
    offset_in_region = int128_get64(int128_sub(clip.start, base));
     /*
     * 准备展开MR为FlatRange,所有的FlatRange组成FlatView
     * clip为待展开MR
     * 更新base为clip的起始,remain为待展开的长度
     */
    base = clip.start;
    remain = clip.size;

    fr.mr = mr;
    fr.dirty_log_mask = mr->dirty_log_mask;
    fr.romd_mode = mr->romd_mode;
    fr.readonly = readonly;

    /* Render the region itself into any gaps left by the current view. */
     /* 开始展开 */
    for (i = 0; i < view->nr && int128_nz(remain); ++i) {
          /*跳过FlatView中在clip前面的FR*/
        if (int128_ge(base, addrrange_end(view->ranges[i].addr))) {
            continue;
        }

          /*
          * 处理clip起始小于当前range起始的情况
          * 展开
          */
        if (int128_lt(base, view->ranges[i].addr.start)) {
               /*计算填空部分大小*/
            now = int128_min(remain,
                             int128_sub(view->ranges[i].addr.start, base));
               /*填充新的Fr信息*/
            fr.offset_in_region = offset_in_region;
            fr.addr = addrrange_make(base, now);
               /*将新的Fr信息填充到插入到FlatView的当前位置,以前该位置往后的FlatRange都向后顺移了一位*/
            flatview_insert(view, i, &fr);
               /*i++执行原来插入位置FlatRange*/
            ++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);
    }

     /*遍历完所有现有的FlatRange后,最后发现还有未展开的内存,这里处理其展开*/    
    if (int128_nz(remain)) {
          /*填入FR的信息*/
        fr.offset_in_region = offset_in_region;
        fr.addr = addrrange_make(base, remain);
          /*插入该FR*/
        flatview_insert(view, i, &fr);
    }
}


如果是alias类型的MR,递归调用render_memory_region,先对源MR进行展开后,在展开自己,注意,这种情况下,offset_in_region就不为0了
如果有子MR,对所有子MR进行递归展开

QEMU内存管理之生成FlatView内存拓扑模型过程分析(基于QEMU2.0.0) - 六六哥 - 六六哥的博客
 
展开完了,回到generate_memory_topology的最后一步,就是对FlatView进行简化合并,如下图的r3,r4,r5就可以合并为一个,都合并为r3,具体的请参考博文《QEMU内存管理之FlatView模型(QEMU2.0.0) 》

参考文章


展开阅读全文

没有更多推荐了,返回首页