设为首页 收藏本站
查看: 3376|回复: 0

[经验分享] 通过KVM_SET_USER_MEMORY_REGION操作虚拟机内存(Kernel 3.10.0 & qemu 2.0.0)

[复制链接]

尚未签到

发表于 2015-10-10 12:25:07 | 显示全部楼层 |阅读模式
先界定几个术语GPA:Guest 物理地址HVA:HOST 虚拟地址MR:Memory Region,QEMU虚拟机中地址空间的管理结构
在QEMU用户态进程中,会调用KVM_SET_USER_MEMORY_REGION来修改虚拟机的内存,kvm_set_user_memory_region就是内核操作KVM_SET_USER_MEMORY_REGION的接口例如:static int kvm_set_user_memory_region(KVMState *s, KVMSlot *slot){    struct kvm_userspace_memory_region mem;    .......   /*通过KVM_SET_USER_MEMORY_REGION进入Kernel*/    mem.guest_phys_addr = slot->start_addr;    mem.userspace_addr = (unsigned long)slot->ram;    mem.memory_size = slot->memory_size;
    return kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);}

我们看一下,内核是处理如何操作的kvm_vm_compat_ioctl-> kvm_vm_ioctl (case KVM_SET_USER_MEMORY_REGION)-> kvm_vm_ioctl_set_memory_region-> kvm_set_memory_region-> __kvm_set_memory_region


int __kvm_set_memory_region(struct kvm *kvm, struct kvm_userspace_memory_region *mem)
{
       struct kvm_memory_slot old, new/*待添加的slot的内容*/;
        
        ......      
/*设置memslot的base_gfn、npages等域*/
  base_gfn = mem->guest_phys_addr >> PAGE_SHIFT;
        /*memory_size为0,就说明npages = 0, npages 在后面会被标记为KVM_MR_DELETE,说明是内存删除动作*/
      npages = mem->memory_size >> PAGE_SHIFT;
      

      /*判断操作的类型*/
      r = -EINVAL;
     if (npages) { /*非删除*/
            if (!old.npages) /*老的页面数为0,说明是新增*/
                       change = KVM_MR_CREATE;
          else { /* Modify an existing slot. */
                    if (base_gfn != old.base_gfn) /*新旧插槽只有gfn不同,那么就是MOVE*/
                           change = KVM_MR_MOVE;
                    else if (new.flags != old.flags)
                                change = KVM_MR_FLAGS_ONLY;
                      else { /* Nothing to change. */
                          r = 0;
                           goto out;
                        }
                }
        } else if (old.npages) { /*待设定的页面数位0,说明是删除*/
             /*memory_size = 0*/
              change = KVM_MR_DELETE;
        } else /* Modify a non-existent slot: disallowed. */
             goto out;

/*
        * 处理和已经存在的memslots的重叠,发现重叠返回-EEXIST
      */
      if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
              /* Check for overlaps */
         r = -EEXIST;
             kvm_for_each_memslot(slot, kvm->memslots) {
                   if ((slot->id >= KVM_USER_MEM_SLOTS) ||
                        (slot->id == mem->slot)/*当前要加入的slot,不管,直接跳过*/)
                         continue;
                        /* new_end > slot_base && new_base < slot_end,说明已经有覆盖该段内存了*/
                       if (!((base_gfn + npages <= slot->base_gfn) ||
                           (base_gfn >= slot->base_gfn + slot->npages)))
                             goto out;
                }
        }

if (change == KVM_MR_CREATE) {
           /*初始化HVA的内容*/
            new.userspace_addr = mem->userspace_addr;
             /*设定memslot中arch相关的内容*/
          if (kvm_arch_create_memslot(&new, npages))
                   goto out_free;
   }

         if ((change == KVM_MR_DELETE) || (change == KVM_MR_MOVE)) {
            r = -ENOMEM;
             slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),GFP_KERNEL);
               if (!slots)
                      goto out_free;
           slot = id_to_memslot(slots, mem->slot);
               slot->flags |= KVM_MEMSLOT_INVALID;

            old_memslots = install_new_memslots(kvm, slots, NULL);

            /* slot was deleted or moved, clear iommu mapping */
             kvm_iommu_unmap_pages(kvm, &old);
          /* From this point no new shadow pages pointing to a deleted,
             * or moved, memslot will be created.
             *
                * validation of sp->gfn happens in:
           *      - gfn_to_hva (kvm_read_guest, gfn_to_pfn)
                 *      - kvm_is_visible_gfn (mmu_check_roots)
            */
              kvm_arch_flush_shadow_memslot(kvm, slot);
                slots = old_memslots;
    }

        ......
}

从上面的代码看来:如果memory_size大于0,那么这次内存操作的动作为“向虚拟机中添加内存或者更新内存布局”如果memory_size等于0,那么这次内存操作的动作为“将guest_phys_addr 为起始地址的那块内存块从虚拟机中删除”
OK,知道了上面的内容,让我们来理解一个QEMU里面的关键函数,这个函数会在后面讲解QEMU内存初始化流程的博文中再次提到。它就是kvm_set_phys_mem,通过该函数,QEMU可以修改虚拟机注册在内核中的内存,达到为虚拟机添加/删除/移动内存的目的
在讲解前,我们先了解一下什么是overlap。overlap就是重叠的意思,见图1 DSC0000.png                              图1: overlap内存所谓slot可以理解为物理机上的一个内存插槽上插入的内存,已有slot,就是虚拟机中已经有的内存当新加入的section管理的地址范围,完全落入已有slot的范围内(见图1),这样就叫做overlap,注意是完全落入哦,重叠一部分不算,参加函数kvm_lookup_overlapping_slot/*

* 在KVMSlot插槽数组中查找同制定范围重叠的KVMSlot

* 注意: 这里完全覆盖才算找到

*/

static KVMSlot *kvm_lookup_overlapping_slot(KVMState *s,  hwaddr start_addr, hwaddr end_addr)

{


    for (i = 0; i < s->nr_slots; i++) {

        KVMSlot *mem = &s->slots;

       .....

          /*找到有地址空间重叠的KVMSlot区域*/

        if (end_addr > mem->start_addr &&

            start_addr < mem->start_addr + mem->memory_size) {

            found = mem;

        }

    }

    return found;

}

那么QEMU会怎么处理这种overlap的情况呢,是这样的:1.  将已有slot分为三个部分,prefix slot + overlap + suffix slot2.  将已有的slot内存,通过KVM_SET_USER_MEMORY_REGION,从内核KVM模块中删除3.  通过KVM_SET_USER_MEMORY_REGION,向内核kvm模块注册添加prefix slot内存4.  通过KVM_SET_USER_MEMORY_REGION,向内核kvm模块注册添加suffix slot内存5.  通过KVM_SET_USER_MEMORY_REGION,向内核kvm模块注册添加overlap内存,起始overlap内存就是本次这次的section的内存OK,这样就将内存加入进去了。
一定有人会问,为什么不直接利用原来的内存,为什么要新分配呢?我的理解是这样的,kvm_set_phys_mem是一个操作内存的统一接口,添加内存和删除内存都通过改接口,如果是删除overlap部分,kvm_set_phys_mem的分段工作就不言而喻了。而且如果添加的section的属性变了,如从RAM变成了ROM,那么重新进行添加也是必要的。
当然,prefix slot 和 suffix slot 的长度可以为0,不用多解释了,就是正好覆盖到起始或结束位置
好了,有了上面的理解,我们就来可以分析一下kvm_set_phys_mem函数了主要流程如图2所示,就不多废话了
DSC0001.png                              图2
下面是代码分析

/*设定虚拟机物理内存*/

static void kvm_set_phys_mem(MemoryRegionSection *section, bool add)

{

    KVMState *s = kvm_state;

    KVMSlot *mem, old;

    int err;

    MemoryRegion *mr = section->mr;

    bool log_dirty = memory_region_is_logging(mr);

    bool writeable = !mr->readonly && !mr->rom_device;

    bool readonly_flag = mr->readonly || memory_region_is_romd(mr);

    hwaddr start_addr = section->offset_within_address_space; /*start_addr为该段section内存GPA的起始地址*/

    ram_addr_t size = int128_get64(section->size); /*start_addr为该段section内存的大小*/

    void *ram = NULL;

    unsigned delta;


    /* kvm works in page size chunks, but the function may be called

       with sub-page size and unaligned start address. */

    /*页面对齐,对页面起始地址和长度进行微调*/

    delta = TARGET_PAGE_ALIGN(size) - size;

    if (delta > size) {

        return;

    }

    start_addr += delta;

    size -= delta;

    size &= TARGET_PAGE_MASK;

    if (!size || (start_addr & ~TARGET_PAGE_MASK)) {

        return;

    }


     /*如果注册的内存是ROM,进行权限校验*/

    if (!memory_region_is_ram(mr)) {

        if (writeable || !kvm_readonly_mem_allowed) {

            return;

        } else if (!mr->romd_mode) {

            /* If the memory device is not in romd_mode, then we actually want

             * to remove the kvm memory slot so all accesses will trap. */

            add = false;

        }

    }


     /*

     * memory_region_get_ram_ptr 是MR管理的内存块的HVA

     * HVA + section->offset_within_region + delta; 就是section所在HVA

     * ram 就是section所在HVA

     */

    ram = memory_region_get_ram_ptr(mr) + section->offset_within_region + delta;


    while (1) {

          /*查找和制定区间重叠的KVMSLOT*/

        mem = kvm_lookup_overlapping_slot(s, start_addr, start_addr + size);

          /* 如果没有重叠,就跳出循环, 直接添加内存*/

        if (!mem) {

            break;

        }


          /*从这里开始,说明找到了mem和制定内存返回重叠了*/


        if (add && start_addr >= mem->start_addr &&

            (start_addr + size <= mem->start_addr + mem->memory_size) &&

            (ram - start_addr == mem->ram - mem->start_addr)) {

            /* The new slot fits into the existing one and comes with

             * identical parameters - update flags and done. */

            kvm_slot_dirty_pages_log_change(mem, log_dirty);

            return;

        }


          /*临时保存原有slot信息*/

        old = *mem;


        if (mem->flags & KVM_MEM_LOG_DIRTY_PAGES) {

            kvm_physical_sync_dirty_bitmap(section);

        }


          /*通过将memory_size设置为0,通过KVM_SET_USER_MEMORY_REGION从内核KVM模块中删除已经注册的内存*/

        /* unregister the overlapping slot */

        mem->memory_size = 0;

          /*设定虚拟机物理内存*/

        err = kvm_set_user_memory_region(s, mem);

        if (err) {

            fprintf(stderr, "%s: error unregistering overlapping slot: %s\n",

                    __func__, strerror(-err));

            abort();

        }


        /* Workaround for older KVM versions: we can't join slots, even not by

         * unregistering the previous ones and then registering the larger

         * slot. We have to maintain the existing fragmentation. Sigh.

         *

         * This workaround assumes that the new slot starts at the same

         * address as the first existing one. If not or if some overlapping

         * slot comes around later, we will fail (not seen in practice so far)

         * - and actually require a recent KVM version. */

          /*

          * 一般不会走这里,因为broken_set_mem_region为0,暂时不看

          */

        if (s->broken_set_mem_region &&

            old.start_addr == start_addr && old.memory_size < size && add) {

            mem = kvm_alloc_slot(s);

            mem->memory_size = old.memory_size;

            mem->start_addr = old.start_addr;

            mem->ram = old.ram;

            mem->flags = kvm_mem_flags(s, log_dirty, readonly_flag);


            err = kvm_set_user_memory_region(s, mem);

            if (err) {

                fprintf(stderr, "%s: error updating slot: %s\n", __func__,

                        strerror(-err));

                abort();

            }


            start_addr += old.memory_size;

            ram += old.memory_size;

            size -= old.memory_size;

            continue;

        }


          /*

          * 通过KVM_SET_USER_MEMORY_REGION,向KVM内核模块注册prefix slot部分内存

          * 通过临时保存的old,计算prefix slot的起始地址和大小         

          */

        /* register prefix slot */

        if (old.start_addr < start_addr) {

            mem = kvm_alloc_slot(s);

            mem->memory_size = start_addr - old.start_addr;

            mem->start_addr = old.start_addr;

            mem->ram = old.ram;

            mem->flags =  kvm_mem_flags(s, log_dirty, readonly_flag);


            err = kvm_set_user_memory_region(s, mem);

            if (err) {

                fprintf(stderr, "%s: error registering prefix slot: %s\n",

                        __func__, strerror(-err));

#ifdef TARGET_PPC

                fprintf(stderr, "%s: This is probably because your kernel's " \

                                "PAGE_SIZE is too big. Please try to use 4k " \

                                "PAGE_SIZE!\n", __func__);

#endif

                abort();

            }

        }


          /*

          * 通过KVM_SET_USER_MEMORY_REGION,向KVM内核模块注册suffix slot部分内存

          * 通过临时保存的old,计算suffix slot的起始地址和大小         

          */

        /* register suffix slot */

        if (old.start_addr + old.memory_size > start_addr + size) {

            ram_addr_t size_delta;


            mem = kvm_alloc_slot(s);

            mem->start_addr = start_addr + size;

            size_delta = mem->start_addr - old.start_addr;

            mem->memory_size = old.memory_size - size_delta;

            mem->ram = old.ram + size_delta;

            mem->flags = kvm_mem_flags(s, log_dirty, readonly_flag);


            err = kvm_set_user_memory_region(s, mem);

            if (err) {

                fprintf(stderr, "%s: error registering suffix slot: %s\n",

                        __func__, strerror(-err));

                abort();

            }

        }

    }


    /* in case the KVM bug workaround already "consumed" the new slot */

    if (!size) {

        return;

    }

    if (!add) {

        return;

    }


     /*新分配一个slot,将section的信息填入其中,并通过KVM_SET_USER_MEMORY_REGION向KVM内核模块中加入/删除内存*/

    mem = kvm_alloc_slot(s);

    mem->memory_size = size;

    mem->start_addr = start_addr;

    mem->ram = ram;

    mem->flags = kvm_mem_flags(s, log_dirty, readonly_flag);


    err = kvm_set_user_memory_region(s, mem);

    if (err) {

        fprintf(stderr, "%s: error registering slot: %s\n", __func__,

                strerror(-err));

        abort();

    }

}

这里补充说明一个关键数据结构MemoryRegionSection,这个结构指定了一段内存,这段内存将被注册到内核KVM模块,进行加入或删除操作。不多说了,上代码struct MemoryRegionSection {

    MemoryRegion *mr;

    AddressSpace *address_space;

     /*

     * 相当于在region内偏移量,region上面挂载了一块从HOST上分配的内存,通过这个offset,就可以计算这个section在HOST内存上的HVA了

     */

    hwaddr offset_within_region;     

   /*该段内存的大小*/

    Int128 size;

     /*

     * AS内偏移量,该值是GPA,相当于从GUEST物理地址0处开始的偏移量,也就是说,这个值是该段内存GPA的起始地址

     * 这很好理解,如果AS代表的是系统内存,那么AS内的偏移量当然是物理地址

     */

    hwaddr offset_within_address_space;

   /*指明是ROM还是RAM*/

    bool readonly;

};   
版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-125081-1-1.html 上篇帖子: Guest OS, Qemu, KVM工作流程 下篇帖子: open /dev/kvm: Permission denied的解决办法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表