在QEMU的内存管理中的FlatView描述了QEMU虚拟机内存平坦展开的情况。
首先看一下FlatView模型
FlatView的原理:
1. 首先FlatView模型是通过FlatView和FlatRange两个对象组成。
2. FlatView是该段内存的整体视图的管理结构,一个FlatView由一组FlatRange组成。
3. 每个FlatRange代表了虚拟机上的一段内存,多个FlagRange就组成了一个内存视图,这些FlatRange在物理地址空间上不一定是相邻的。
3. 每个FlatView代表了某一类内存的组合,用作特殊的用途(如系统内存空间,MMIO内存地址空间),通常一个FlatView同一个特定用途的Address_Space进行关联
4. 每个FlatRange所界定的GUEST物理地址空间范围通过AddrRange所界定,有关AddrRange的内存,请参考
《QEMU的AddrRange地址空间对象模型算法总结(QEMU2.0.0)》
5. FlatRange数组在FlatView初始化的时候为0个,也就是没有分配数组。当进行flatview_insert()的操作的时候,才会动态分配出来
6. 为了简化FlatView,通常将地址空间上连续的FlatRange进行合并,合并为1个FlatRange。如图中的r3,r4,r5就可以进行合并的区间,合并后都合并为r3。r4和r5的内容将被后面的FlatRange的数组元素覆盖掉
关于计算地址的方法和其他128bit的算法,请参考
《QEMU的128位算法集合(基于QEMU2.0.0)》
了解了上面的原理,相关代码如下
struct FlatRange { MemoryRegion *mr; hwaddr offset_in_region; AddrRange addr; uint8_t dirty_log_mask; bool romd_mode; bool readonly; }; /* * FlatView是将树状AS的平行展开,可以想象为一个GUEST内存条,所以FlatView里面都是GPA相关的内容 * FlatView里含有多个FlatRange,每个FlatRange代表了一段内存, * 所有的FlatRange共同构成了FlatView的Guest内存条 * 每个FlatRange通过AddrRange标记该段GUEST内存的大小和长度 */ struct FlatView { unsigned ref; /*引用计数*/ FlatRange *ranges; /*指向FlatRange数组*/ unsigned nr; /*已经使用了多少个FlatRange*/ unsigned nr_allocated; /*总共分配了多少FlatRange*/ }; /* * 遍历FlatView下所有的FlatRange */ #define FOR_EACH_FLAT_RANGE(var, view) \ for (var = (view)->ranges; var < (view)->ranges + (view)->nr; ++var) /* * 判断两个FlatRange是否代表相同的地址空间 * AddrRange的判断请参见<QEMU的AddrRange地址空间对象模型算法总结(QEMU2.0.0)> */ static bool flatrange_equal(FlatRange *a, FlatRange *b) { ..... } /* * 初始化一个FlatView,开始的时候,内部含有的0个FlatRange */ static void flatview_init(FlatView *view) { view->ref = 1; view->ranges = NULL; view->nr = 0; view->nr_allocated = 0; } /* * 向Flatview的指定位置中插入一个FlatRange */ static void flatview_insert(FlatView *view, unsigned pos, FlatRange *range) { /* * 如果FlatRange数组占满了,那么新分配已开内存(原来的2倍大小) * 注意这里用的是realloc,是扩大内存大小,不改变原来指针的位置, */ if (view->nr == view->nr_allocated) { view->nr_allocated = MAX(2 * view->nr, 10); view->ranges = g_realloc(view->ranges, view->nr_allocated * sizeof(*view->ranges)); } /* * 将指定位置之后的内存都向后搬移一下,空出pos的位置 */ memmove(view->ranges + pos + 1, view->ranges + pos, (view->nr - pos) * sizeof(FlatRange)); /* * 将新的FlatRange的内容记录到pos位置,完成插入 */ view->ranges[pos] = *range; /* 每增加一个Range都会让mr的引用加1,因为FlatRange中mr成员会引用mr*/ memory_region_ref(range->mr); /* 使用数量加一 */ ++view->nr; } /* * 销毁一个FlatView */ static void flatview_destroy(FlatView *view) { int i; /* 对MR引用计数减少1 */ for (i = 0; i < view->nr; i++) { memory_region_unref(view->ranges[i].mr); } /*释放FlatRange数组*/ g_free(view->ranges); /*释放FlatView*/ g_free(view); } /*增加FlatView引用计数*/ static void flatview_ref(FlatView *view) { atomic_inc(&view->ref); } /*减少FlatView引用计数,减少为0的时候,销毁FlatView*/ static void flatview_unref(FlatView *view) { if (atomic_fetch_dec(&view->ref) == 1) { flatview_destroy(view); } } /* * 判断r1和r2是否可以合并,可以合并返回true * * 可以合并的条件如下: * 1. r1.end(GPA) == r2.start(GPA) * 2. 属于同一个MR * 3. r1.offset+size == r2.offset(在MR内的偏移量正好可以对接上) * 4. dirty_log相同,迁移使用 * 5. 读写属性相同 * 6. 读写模式相同 */ static bool can_merge(FlatRange *r1, FlatRange *r2) { ...... } /* * 通过合并相邻的FlatRange,简化FlatView */ static void flatview_simplify(FlatView *view) { unsigned i, j; i = 0; /*遍历所有的FlatRange,进行range的合并*/ while (i < view->nr) { j = i + 1; /* * 将从i开始之后所有的能合并的内存都合并 * 1. 所有能合并的都往 i 上做合并 * 2. j指向可以合并的最后一个range的后一个 */ while (j < view->nr && can_merge(&view->ranges[j-1], &view->ranges[j])) { /*可以合并的话,增加i的大小*/ int128_addto(&view->ranges[i].addr.size, view->ranges[j].addr.size); ++j; } /* i指向了下一个待合并填充的位置*/ ++i; /* 进行内存搬移,将合并了的内存覆盖掉 */ memmove(&view->ranges[i], &view->ranges[j], (view->nr - j) * sizeof(view->ranges[j])); /*修改已用FlatRange的数量,减少已经合并的数量*/ view->nr -= j - i; } } |
参考文章