QEMU内存分析(二):平坦内存展开

简介:

AddressSpace 的root域及其子树共同构成了 Guest 的物理地址空间,但这些都是在 QEMU 侧定义的。要传入 KVM/HAXM 进行设置时,复杂的树状结构是不利于内核进行处理的,因此需要将其转换为一个“平坦”的地址模型,也就是一个从零开始、只包含地址信息的数据结构,这在 QEMU 中通过 FlatView 来表示。每个 AddressSpace 都有一个与之对应的 FlatView 指针current_map,表示其对应的平面展开视图
 
一:  FlatView生成
       在第一章第一节的AddressSpace初始化中提到会调用memory_map_init来生成地址空间。在memory_map_init函数生成地址空间时同时也生成了一张FlatView用于描述平坦视图,调用关系如下:    
main()                       // vl.c
cpu_exec_init_all()          // exec.c
  memory_map_init()          // exec.c
    address_space_init()        //memory.c
      address_space_update_topology()     //memory.c
        generate_memory_topology()    //memory.c
          render_memory_region()    //memory.c
          flatview_simplify()        //memory.c
            address_space_set_flatview()    //memory.c
 

二: 关键函数分析

2.1  generate_memory_topology

/* Render a memory topology into a list of disjoint absolute ranges. */
/*
* 将MR所管理的内存展开成FlatView后返回
*/
static FlatView *generate_memory_topology(MemoryRegion *mr)
{
    int i;
    FlatView *view;
    /*新分配一个FlatView并初始化*/
    view = flatview_new(mr);

    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);

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

2.2 render_memory_region

/* 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)
{
    /* view,代表线性空间试图,维护MemoryRegion和 线性地址的关系(用于gpa→hva的转换)
    mr则为要渲染的MemoryRegion
    base 标示该MemoryRegion对应最根层MemoryRegion的偏移(最根层的偏移一般为0 ,现在已知的就system_memory和 system_io两个地址空间, 分别代表内存空间和io空间)
    clip 表示父空间的地址范围, 子空间的地址范围是不允许落在父空间的范围外的
    readonly 则表示该区域是否为只读的,用于赋值FlatRange*/
    MemoryRegion *subregion;
    unsigned i;
    hwaddr offset_in_region;
    Int128 remain;
    Int128 now;
    FlatRange fr;
    AddrRange tmp;

    if (!mr->enabled) {
        return;
    }
    /* base修改为本mr的 起始地址,mr作为subregion时addr为mr在父container中的偏移 */
    /* 在起始时root节点的base是0,addr对应的也是0 */
    int128_addto(&base, int128_make64(mr->addr));
    readonly |= mr->readonly;

    /* 生成int128类型的地址空间,{base, size} */
    tmp = addrrange_make(base, mr->size);

    /* 确保子空间tmp的地址访问在clip中 */
    if (!addrrange_intersects(tmp, clip)) {
        return;
    }
    /* 更新clip的范围为tmp和原始clip的交集,即mr的有效空间 */
    clip = addrrange_intersection(tmp, clip);
    
    /* 处理别名的情况, 把别名指向的空间相对于当前地址空间的偏移进行渲染 */
    //根据这几行代码,我们可以得知alias的GPA = origin mr的addr + alias mr的alias_offset
    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都已经展开了
    */

    /* 更新offset_in_region,offset_in_region是mr的gpa与clip的偏移量,
    由于我们从clip.start开始render,因此将作为后面fr的offset_in_region,
    以后用来计算本FR对应MR的物理内存的HVA */
    //只有是alias类型的MemoryRegion,相应的offset_in_region才不为0
    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;

    /* Render the region itself into any gaps left by the current view. */
    /* qemu内存建模是通过优先级来实现内存地址的覆盖, 优先级高的MemoryRegion 先被转换为 FlatRang, 
    后面渲染的时候低优先级的FlatRang不覆盖高优先级的FlatRang, 低优先级的FlatRang如果被高优先级
    的FlatRang截断则只保存不被高优先级遮盖的部分到flatview, 
    这是render_memory_region 的682行到708行的主要工作 */
    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));
            /* FlatRange的offset_in_region 变量表示该FlatRange相对所在MemoryRegion的位置
               (因为一个MemoryRegion可能被高优先级的MemoryRegion截断成多段,
               所以offset_in_region不一定为0) 
            */
            fr.offset_in_region = offset_in_region;
            /* fr.addr 标示该FlatRange在线性地址空间的偏移 */
            fr.addr = addrrange_make(base, now);
            /* 将新的Fr信息填充到插入到FlatView的当前位置,以前该位置往后的FlatRange都向后顺移了一位 */
            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);
    }
     /* 遍历完所有现有的FlatRange后,最后发现还有未展开的内存,这里处理其展开 */    
    if (int128_nz(remain)) {
        fr.offset_in_region = offset_in_region;
        fr.addr = addrrange_make(base, remain);
        flatview_insert(view, i, &fr);
    }
}

2.3  flatview_simplify

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

    i = 0;
    while (i < view->nr) {
        j = i + 1;
        while (j < view->nr
                /* 对比两个flatRange各个属性相同则可以merge */
               && can_merge(&view->ranges[j-1], &view->ranges[j])) {
               /* 依次将后一个range的size加到第一个           */
            int128_addto(&view->ranges[i].addr.size, view->ranges[j].addr.size);
            ++j;
        }
        ++i;
        /* 从j的 位置开始,将之后的move到i的后面,即覆盖掉i和j之间已经合并的内容                              */  
        memmove(&view->ranges[i], &view->ranges[j],
                (view->nr - j) * sizeof(view->ranges[j]));
        view->nr -= j - i;
    }
}
 

参考:

https://blog.csdn.net/woai110120130/article/details/102311622

原文地址:https://www.cnblogs.com/edver/p/14470718.html