# 添加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内核模块。
