添加MemoryRegion

本章我们将以添加MemoryRegion为线索,动态得考察各个数据结构及其之间的变化。

那添加MemoryRegion的线索是谁呢? 就是这个

memory_region_transaction_commit()

那就以此开始。

精简的call flow

让我们先从全局观察这个函数,获得一个全局的概念。

memory_region_transaction_commit(), update topology or ioeventfds
     flatviews_reset()
         flatviews_init()
             flat_views = g_hash_table_new_full()
             empty_view = generate_memory_topology(NULL);
         generate_memory_topology()
     MEMORY_LISTENER_CALL_GLOBAL(begin, Forward)
     address_space_set_flatview()
         address_space_update_topology_pass(false)
         address_space_update_topology_pass(true)
     address_space_update_ioeventfds()
         address_space_add_del_ioeventfds()
     MEMORY_LISTENER_CALL_GLOBAL(commit, Forward)

从上面的精简版来看,添加一个MemoryRegion需要做如下几件事:

  • flatviews_reset: 重构所有AddressSpace的flatview

  • MEMORY_LISTENER_CALL_GLOBAL(begin, Forward)

  • address_space_set_flatview: 根据变化添加删除region

  • address_space_update_ioeventfds: 根据变化添加删除eventfd

  • MEMORY_LISTENER_CALL_GLOBAL(commit, Forward)

所以总的来讲也就这五个步骤,其中第一个步骤就是将MemoryRegion转换为FlatView,而其余的四个步骤都是根据FlatView的变化做出相应的调整。

MemoryListener

为了辅助地址空间的动态变化,还需要了解一个数据结构 MemoryListener。

  MemoryListerner
  +---------------------------+
  |begin                      |
  |commit                     |
  +---------------------------+
  |region_add                 |
  |region_del                 |
  +---------------------------+
  |eventfd_add                |
  |eventfd_del                |
  +---------------------------+
  |log_start                  |
  |log_stop                   |
  +---------------------------+

有经验的朋友到这估计猜出来了,这个就是一个回调函数的集合,用在必要的时间点调用到这些钩子。

那在什么地方去调用这些回调函数呢?最主要的一共有两个宏定义

  • MEMORY_LISTENER_CALL_GLOBAL

  • MEMORY_LISTENER_CALL

这两个宏定义长得非常像,只有一点点的差别。那就是调用的MemoryListener的链表不同。

在memory_listener_register注册MemoryListener的时候,会注册到两个地方

  • memory_listeners

  • as->listeners

也就是一个是全局的链表,一个是AddressSpace自身的链表。

所以我们可以总结一下添加MemoryRegion时需要做的操作

  • 根据新的MemoryRegion树生成FlatView

  • 比较新旧FlatView调用MemoryListener进行修改

以KVM为例

最后我们以KVM为例,看看kvm是如何将客户机的内存通知给内核模块的。

既然知道了上述的流程概述,那么就知道重点是看kvm注册的MemoryListener的回调函数是什么。

查看代码,发现有两个重要的成员是:

  kml->listener.region_add = kvm_region_add;
  kml->listener.region_del = kvm_region_del;

那我们以region_add为例,看看添加一个MemoryRegion时做的工作。

    kvm_region_add
        kvm_set_phys_mem
            kvm_set_user_memory_region
                kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);

所以最后是调用了KVM_SET_USER_MEMORY_REGION这个ioctl将qemu用户态的内存传给了kvm内核模块。

Last updated