zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    博客园修改 markdown 编辑器代码高亮样式、字体、字体大小等
    Ubuntu 安装 Insomnia
    Golang 中的反向代理(ReverseProxy) 介绍与使用
    Ubuntu 安装 httpserver
    Golang net/http 标准库原理解读与源码分析
    博客园 markdown 编辑器中代码缩进间距很大的解决方法
    git fetch 的作用与原理
    JetBrains IDE 中代码使用空格缩进,设置Tab键为4个空格
    packmol建模流程计算
    Leaflet中使用Leaflet.fullscreen插件实现全屏效果
  • 原文地址:https://www.cnblogs.com/edver/p/14470718.html
Copyright © 2011-2022 走看看