接前一篇文章: QEMU源码全解析 —— 内存虚拟化(8)
本文内容参考:
《 QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社
浅谈QEMU Memory Region 与 Address Space
特此致谢!
2. QEMU虚拟机内存初始化
上一回沿着以下代码路径:
qemu_init()
---> qemu_create_machine()
---> cpu_exec_init_all()
---> memory_map_init()
---> address_space_init()
---> address_space_update_topology()
讲到了address_space_update_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_set_flatview函数进行解析。address_space_set_flatview函数也在softmmu/memory.c中,代码如下:
static void address_space_set_flatview(AddressSpace *as)
{
FlatView *old_view = address_space_to_flatview(as);
MemoryRegion *physmr = memory_region_get_flatview_root(as->root);
FlatView *new_view = g_hash_table_lookup(flat_views, physmr);
assert(new_view);
if (old_view == new_view) {
return;
}
if (old_view) {
flatview_ref(old_view);
}
flatview_ref(new_view);
if (!QTAILQ_EMPTY(&as->listeners)) {
FlatView tmpview = { .nr = 0 }, *old_view2 = old_view;
if (!old_view2) {
old_view2 = &tmpview;
}
address_space_update_topology_pass(as, old_view2, new_view, false);
address_space_update_topology_pass(as, old_view2, new_view, true);
}
/* Writes are protected by the BQL. */
qatomic_rcu_set(&as->current_map, new_view);
if (old_view) {
flatview_unref(old_view);
}
/* Note that all the old MemoryRegions are still alive up to this
* point. This relieves most MemoryListeners from the need to
* ref/unref the MemoryRegions they get---unless they use them
* outside the iothread mutex, in which case precise reference
* counting is necessary.
*/
if (old_view) {
flatview_unref(old_view);
}
}
address_space_set_flatview函数一上来,先将老的flatview和新的flatview进行比较。如果相同,直接返回。代码片段如下:
FlatView *old_view = address_space_to_flatview(as);
MemoryRegion *physmr = memory_region_get_flatview_root(as->root);
FlatView *new_view = g_hash_table_lookup(flat_views, physmr);
assert(new_view);
if (old_view == new_view) {
return;
}
前文书讲到过,由于这里是内存初始化的阶段,因此在address_space_update_topology函数中,全局的flat_views中肯定找不到,这才调用了generate_memory_topology函数,第一次生成了FlatView。然后才调用了address_space_set_flatview函数。
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);
}
因此,老的flatview和新的flatview肯定是一样的。
FlatView *old_view = address_space_to_flatview(as);
MemoryRegion *physmr = memory_region_get_flatview_root(as->root);
FlatView *new_view = g_hash_table_lookup(flat_views, physmr);
assert(new_view);
if (old_view == new_view) {
return;
}
如果老的flatview和新的flatview不相同,则说明内存结构发生了变化,会调用address_space_update_topology_pass函数调用所有的listener。
address_space_update_topology_pass函数也在同文件(softmmu/memory.c)中,代码如下:
static void address_space_update_topology_pass(AddressSpace *as,
const FlatView *old_view,
const FlatView *new_view,
bool adding)
{
unsigned iold, inew;
FlatRange *frold, *frnew;
/* Generate a symmetric difference of the old and new memory maps.
* Kill ranges in the old map, and instantiate ranges in the new map.
*/
iold = inew = 0;
while (iold < old_view->nr || inew < new_view->nr) {
if (iold < old_view->nr) {
frold = &old_view->ranges[iold];
} else {
frold = NULL;
}
if (inew < new_view->nr) {
frnew = &new_view->ranges[inew];
} else {
frnew = NULL;
}
if (frold
&& (!frnew
|| int128_lt(frold->addr.start, frnew->addr.start)
|| (int128_eq(frold->addr.start, frnew->addr.start)
&& !flatrange_equal(frold, frnew)))) {
/* In old but not in new, or in both but attributes changed. */
if (!adding) {
flat_range_coalesced_io_del(frold, as);
MEMORY_LISTENER_UPDATE_REGION(frold, as, Reverse, region_del);
}
++iold;
} else if (frold && frnew && flatrange_equal(frold, frnew)) {
/* In both and unchanged (except logging may have changed) */
if (adding) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_nop);
if (frnew->dirty_log_mask & ~frold->dirty_log_mask) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, log_start,
frold->dirty_log_mask,
frnew->dirty_log_mask);
}
if (frold->dirty_log_mask & ~frnew->dirty_log_mask) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Reverse, log_stop,
frold->dirty_log_mask,
frnew->dirty_log_mask);
}
}
++iold;
++inew;
} else {
/* In new */
if (adding) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_add);
flat_range_coalesced_io_add(frnew, as);
}
++inew;
}
}
}
其中就是通过MEMORY_LISTENER_UPDATE_REGION,进而调用MEMORY_LISTENER_CALL 通知所有listener的。
MEMORY_LISTENER_UPDATE_REGION是一个宏,就在softmmu/memory.c中定义,如下:
/* No need to ref/unref .mr, the FlatRange keeps it alive. */
#define MEMORY_LISTENER_UPDATE_REGION(fr, as, dir, callback, _args...) \
do { \
MemoryRegionSection mrs = section_from_flat_range(fr, \
address_space_to_flatview(as)); \
MEMORY_LISTENER_CALL(as, callback, dir, &mrs, ##_args); \
} while(0)
更多内容请看下回。