接前一篇文章: QEMU源码全解析 —— 内存虚拟化(17)
本文内容参考:
《 QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社
浅谈QEMU Memory Region 与 Address Space
QEMU内存分析(一):内存虚拟化关键结构体 - Edver - 博客园
特此致谢!
2. QEMU虚拟机内存初始化
本回继续对于QEMU内存平坦化的核心函数 —— render_memory_region()进行深入解析。为了便于理解和回顾,再次贴出render_memory_region函数代码,在softmmu/memory.c中,如下:
/* 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,
bool nonvolatile)
{
MemoryRegion *subregion;
unsigned i;
hwaddr offset_in_region;
Int128 remain;
Int128 now;
FlatRange fr;
AddrRange tmp;
if (!mr->enabled) {
return;
}
int128_addto(&base, int128_make64(mr->addr));
readonly |= mr->readonly;
nonvolatile |= mr->nonvolatile;
tmp = addrrange_make(base, mr->size);
if (!addrrange_intersects(tmp, clip)) {
return;
}
clip = addrrange_intersection(tmp, clip);
if (mr->alias) {
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, nonvolatile);
return;
}
/* Render subregions in priority order. */
QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) {
render_memory_region(view, subregion, base, clip,
readonly, nonvolatile);
}
if (!mr->terminates) {
return;
}
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;
fr.nonvolatile = nonvolatile;
/* Render the region itself into any gaps left by the current view. */
for (i = 0; i < view->nr && int128_nz(remain); ++i) {
if (int128_ge(base, addrrange_end(view->ranges[i].addr))) {
continue;
}
if (int128_lt(base, view->ranges[i].addr.start)) {
now = int128_min(remain,
int128_sub(view->ranges[i].addr.start, base));
fr.offset_in_region = offset_in_region;
fr.addr = addrrange_make(base, now);
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);
}
if (int128_nz(remain)) {
fr.offset_in_region = offset_in_region;
fr.addr = addrrange_make(base, remain);
flatview_insert(view, i, &fr);
}
}
上一回讲到了以下代码片段:
/* Render the region itself into any gaps left by the current view. */
for (i = 0; i < view->nr && int128_nz(remain); ++i) {
……
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);
}
这段代码用来越过fr2。当然也会存在其它情况,比如mr2尾部落在了fr2中,因此这里通过一个int128_min函数来判断。
now = int128_sub(int128_min(int128_add(base, remain),
addrrange_end(view->ranges[i].addr)),
base);
此时,第一个深色区域就插入完了。其base为fr2的结束地址;offset_in_region为fr2的长度与左边深色部分的和;remain为右侧深色部分的长度。
接下来就该是下一轮循环了。在下一轮循环中,把右侧深色区域所构成的FlatRange插入到view中。
以此类推,最终render_memory_region函数将返回,返回的时候,view中已经填满了以mr1为根的FlatRange。
至此,render_memory_region函数就解析完了。回顾一下其整体功能:
render_memory_region函数返回之后,回到generate_memory_topology函数中(softmmu/memory.c中)。
/* Render a memory topology into a list of disjoint absolute ranges. */
static FlatView *generate_memory_topology(MemoryRegion *mr)
{
int i;
FlatView *view;
view = flatview_new(mr);
if (mr) {
render_memory_region(view, mr, int128_zero(),
addrrange_make(int128_zero(), int128_2_64()),
false, false);
}
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;
}
generate_memory_topology函数接着调用flatview_simplify函数。该函数在softmmu/memory.c中,代码如下:
/* Attempt to simplify a view by merging adjacent ranges */
static void flatview_simplify(FlatView *view)
{
unsigned i, j, k;
i = 0;
while (i < view->nr) {
j = i + 1;
while (j < view->nr
&& can_merge(&view->ranges[j-1], &view->ranges[j])) {
int128_addto(&view->ranges[i].addr.size, view->ranges[j].addr.size);
++j;
}
++i;
for (k = i; k < j; k++) {
memory_region_unref(view->ranges[k].mr);
}
memmove(&view->ranges[i], &view->ranges[j],
(view->nr - j) * sizeof(view->ranges[j]));
view->nr -= j - i;
}
}
generate_memory_topology函数中的can_merge函数在同文件中(就在上边),代码如下:
static bool can_merge(FlatRange *r1, FlatRange *r2)
{
return int128_eq(addrrange_end(r1->addr), r2->addr.start)
&& r1->mr == r2->mr
&& int128_eq(int128_add(int128_make64(r1->offset_in_region),
r1->addr.size),
int128_make64(r2->offset_in_region))
&& r1->dirty_log_mask == r2->dirty_log_mask
&& r1->romd_mode == r2->romd_mode
&& r1->readonly == r2->readonly
&& r1->nonvolatile == r2->nonvolatile;
}
用来判断两个FlatRange是否可以合并。如果两个FlatRange是紧邻的,并且各种属性皆相同,则将这两个FlatRange是可以合并的,就将这两者合并起来。
再回到调用generate_memory_topology函数的address_space_update_topology函数中(也在softmmu/memory.c中):
static void address_space_update_topology(AddressSpace *as)
{
MemoryRegion *physmr = memory_region_get_flatview_root(as->root);
flatviews_init();
if (!g_hash_table_lookup(flat_views, physmr)) {
generate_memory_topology(physmr);
}
address_space_set_flatview(as);
}
每一次虚拟机在进行内存提交时,在address_space_update_topology函数中都会调用generate_memory_topology函数,生成当前虚拟机内存的低平坦表示。
qemu_init()
---> qemu_create_machine()
---> cpu_exec_init_all()
---> memory_map_init()
---> address_space_init()
---> address_space_update_topology()
欲知后事如何,且看下回分解。