QEMU内存分析(二):平坦内存展开
简介:
AddressSpace 的
root
域及其子树共同构成了 Guest 的物理地址空间,但这些都是在 QEMU 侧定义的。要传入 KVM/HAXM 进行设置时,复杂的树状结构是不利于内核进行处理的,因此需要将其转换为一个“平坦”的地址模型,也就是一个从零开始、只包含地址信息的数据结构,这在 QEMU 中通过 FlatView 来表示。每个 AddressSpace 都有一个与之对应的 FlatView 指针current_map
,表示其对应的平面展开视图一: FlatView生成
在第一章第一节的AddressSpace初始化中提到会调用memory_map_init来生成地址空间。在memory_map_init函数生成地址空间时同时也生成了一张FlatView用于描述平坦视图,调用关系如下:
二: 关键函数分析
2.1 generate_memory_topology
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
参考:
https://blog.csdn.net/woai110120130/article/details/102311622