kernel 3.10代碼分析--KVM-KVM_SET_USER_MEMORY_REGION流程

1、基本原理
如之前分析,kvm虛擬機實際運行於qemu-kvm的進程上下文中,因此,需要建立虛擬機的物理內存空間(GPA)與qemu-kvm進程的虛擬地址空間(HVA)的映射關係。
虛擬機的物理地址空間實際也是不連續的,分成不同的內存區域(slot),因爲物理地址空間中通常還包括BIOS、MMIO、顯存、ISA保留等部分。

qemu-kvm通過ioctl vm指令KVM_SET_USER_MEMORY_REGION來爲虛擬機設置內存。主要建立guest物理地址空間中的內存區域與qemu-kvm虛擬地址空間中的內存區域的映射,從而建立其從GVA到HVA的對應關係,該對應關係主要通過kvm_mem_slot結構體保存,所以實質爲設置kvm_mem_slot結構體
本文簡介ioctl vm指令KVM_SET_USER_MEMORY_REGION在內核中的執行流程,qemu-kvm用戶態部分暫不包括。

2、基本流程
ioctl vm指令KVM_SET_USER_MEMORY_REGION在內核主要執行流程如下:
kvm_vm_ioctl()
    kvm_vm_ioctl_set_memory_region()
        kvm_set_memory_region()
            __kvm_set_memory_region()
                kvm_iommu_unmap_pages() // 原來的slot需要刪除,所以需要unmap掉相應的內存區域
                install_new_memslots() //將new分配的memslot寫入kvm->memslots[]數組中
                kvm_free_physmem_slot() // 釋放舊內存區域相應的物理內存(HPA)

3、代碼分析
kvm_mem_slot結構

點擊(此處)摺疊或打開

  1. /*
  2.   * 由於GPA不能直接用於物理 MMU 進行尋址,所以需要將GPA轉換爲HVA,
  3.   * kvm中利用 kvm_memory_slot 數據結構來記錄每一個地址區間(Guest中的物理
  4.   * 地址區間)中GPA與HVA的映射關係
  5.   */
  6. struct kvm_memory_slot {
  7.     // 虛擬機物理地址(即GPA)對應的頁框號
  8.     gfn_t base_gfn;
  9.     // 當前slot中包含的page數
  10.     unsigned long npages;
  11.     // 髒頁位圖
  12.     unsigned long *dirty_bitmap;
  13.     // 架構相關的部分
  14.     struct kvm_arch_memory_slot arch;
  15.     /* 
  16.      * GPA對應的Host虛擬地址(HVA),由於虛擬機都運行在qemu的地址空間中
  17.      * 而qemu是用戶態程序,所以通常使用根模式下用戶地址空間。
  18.      */
  19.     unsigned long userspace_addr;
  20.     u32 flags;
  21.     short id;
  22. };

kvm_vm_ioctl()
:

點擊(此處)摺疊或打開

  1. /*
  2.   * kvm ioctl vm指令的入口,傳入的fd爲KVM_CREATE_VM中返回的fd。
  3.   * 主要用於針對VM虛擬機進行控制,如:內存設置、創建VCPU等。
  4.   */
  5. static long kvm_vm_ioctl(struct file *filp,
  6.              unsigned int ioctl, unsigned long arg)
  7. {
  8.     struct kvm *kvm = filp->private_data;
  9.     void __user *argp = (void __user *)arg;
  10.     int r;

  11.     if (kvm->mm != current->mm)
  12.         return -EIO;
  13.     switch (ioctl) {
  14.     // 創建VCPU
  15.     case KVM_CREATE_VCPU:
  16.         r = kvm_vm_ioctl_create_vcpu(kvm, arg);
  17.         break;
  18.     // 建立guest物理地址空間中的內存區域與qemu-kvm虛擬地址空間中的內存區域的映射
  19.     case KVM_SET_USER_MEMORY_REGION: {
  20.         // 存放內存區域信息的結構體,該內存區域從qemu-kvm進程的用戶地址空間中分配
  21.         struct kvm_userspace_memory_region kvm_userspace_mem;

  22.         r = -EFAULT;
  23.         // 從用戶態拷貝相應數據到內核態,入參argp指向用戶態地址
  24.         if (copy_from_user(&kvm_userspace_mem, argp,
  25.                         sizeof kvm_userspace_mem))
  26.             goto out;
  27.         // 進入實際處理流程
  28.         r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem);
  29.         break;
  30.     }
  31. ...

kvm_vm_ioctl()-->kvm_vm_ioctl_set_memory_region()-->kvm_set_memory_region()-->__kvm_set_memory_region()

點擊(此處)摺疊或打開

  1. /*
  2.   * 建立guest物理地址空間中的內存區域與qemu-kvm虛擬地址空間中的內存區域的映射
  3.   * 相應信息由uerspace_memory_region參數傳入,而其源頭來自於用戶態qemu-kvm。每次
  4.   * 調用設置一個內存區間。內存區域可以不連續(實際的物理內存區域也經常不連
  5.   * 續,因爲有可能有保留內存)
  6.   */
  7. int __kvm_set_memory_region(struct kvm *kvm,
  8.              struct kvm_userspace_memory_region *mem)
  9. {
  10.     int r;
  11.     gfn_t base_gfn;
  12.     unsigned long npages;
  13.     struct kvm_memory_slot *slot;
  14.     struct kvm_memory_slot old, new;
  15.     struct kvm_memslots *slots = NULL, *old_memslots;
  16.     enum kvm_mr_change change;

  17.     // 標記檢查
  18.     r = check_memory_region_flags(mem);
  19.     if (r)
  20.         goto out;

  21.     r = -EINVAL;
  22.     /* General sanity checks */
  23.     // 合規檢查,防止用戶態惡意傳參,導致安全漏洞
  24.     if (mem->memory_size & (PAGE_SIZE - 1))
  25.         goto out;
  26.     if (mem->guest_phys_addr & (PAGE_SIZE - 1))
  27.         goto out;
  28.     /* We can read the guest memory with __xxx_user() later on. */
  29.     if ((mem->slot < KVM_USER_MEM_SLOTS) &&
  30.      ((mem->userspace_addr & (PAGE_SIZE - 1)) ||
  31.      !access_ok(VERIFY_WRITE,
  32.             (void __user *)(unsigned long)mem->userspace_addr,
  33.             mem->memory_size)))
  34.         goto out;
  35.     if (mem->slot >= KVM_MEM_SLOTS_NUM)
  36.         goto out;
  37.     if (mem->guest_phys_addr + mem->memory_size < mem->guest_phys_addr)
  38.         goto out;
  39.     // 將kvm_userspace_memory_region->slot轉換爲kvm_mem_slot結構,該結構從kvm->memslots獲取
  40.     slot = id_to_memslot(kvm->memslots, mem->slot);
  41.     // 內存區域起始位置在Guest物理地址空間中的頁框號
  42.     base_gfn = mem->guest_phys_addr >> PAGE_SHIFT;
  43.     // 內存區域大小轉換爲page單位
  44.     npages = mem->memory_size >> PAGE_SHIFT;

  45.     r = -EINVAL;
  46.     if (npages > KVM_MEM_MAX_NR_PAGES)
  47.         goto out;

  48.     if (!npages)
  49.         mem->flags &= ~KVM_MEM_LOG_DIRTY_PAGES;

  50.     new = old = *slot;

  51.     new.id = mem->slot;
  52.     new.base_gfn = base_gfn;
  53.     new.npages = npages;
  54.     new.flags = mem->flags;

  55.     r = -EINVAL;
  56.     if (npages) {
  57.         // 判斷是否需新創建內存區域
  58.         if (!old.npages)
  59.             change = KVM_MR_CREATE;
  60.         // 判斷是否修改現有的內存區域
  61.         else { /* Modify an existing slot. */
  62.             // 修改的區域的HVA不同或者大小不同或者flag中的
  63.             // KVM_MEM_READONLY標記不同,直接退出。
  64.             if ((mem->userspace_addr != old.userspace_addr) ||
  65.              (npages != old.npages) ||
  66.              ((new.flags ^ old.flags) & KVM_MEM_READONLY))
  67.                 goto out;
  68.             /*
  69.              * 走到這,說明被修改的區域HVA和大小都是相同的
  70.              * 判斷區域起始的GFN是否相同,如果是,則說明需
  71.              * 要在Guest物理地址空間中move這段區域,設置KVM_MR_MOVE標記
  72.              */
  73.             if (base_gfn != old.base_gfn)
  74.                 change = KVM_MR_MOVE;
  75.             // 如果僅僅是flag不同,則僅修改標記,設置KVM_MR_FLAGS_ONLY標記
  76.             else if (new.flags != old.flags)
  77.                 change = KVM_MR_FLAGS_ONLY;
  78.             // 否則,啥也不幹
  79.             else { /* Nothing to change. */
  80.                 r = 0;
  81.                 goto out;
  82.             }
  83.         }
  84.     } else if (old.npages) {/*如果新設置的區域大小爲0,而老的區域大小不爲0,則表示需要刪除原有區域。*/
  85.         change = KVM_MR_DELETE;
  86.     } else /* Modify a non-existent slot: disallowed. */
  87.         goto out;

  88.     if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
  89.         /* Check for overlaps */
  90.         r = -EEXIST;
  91.         // 檢查現有區域中是否重疊的
  92.         kvm_for_each_memslot(slot, kvm->memslots) {
  93.             if ((slot->id >= KVM_USER_MEM_SLOTS) ||
  94.              (slot->id == mem->slot))
  95.                 continue;
  96.             if (!((base_gfn + npages <= slot->base_gfn) ||
  97.              (base_gfn >= slot->base_gfn + slot->npages)))
  98.                 goto out;
  99.         }
  100.     }

  101.     /* Free page dirty bitmap if unneeded */
  102.     if (!(new.flags & KVM_MEM_LOG_DIRTY_PAGES))
  103.         new.dirty_bitmap = NULL;

  104.     r = -ENOMEM;
  105.     // 如果需要創建新區域
  106.     if (change == KVM_MR_CREATE) {
  107.         new.userspace_addr = mem->userspace_addr;
  108.         // 設置新的內存區域架構相關部分
  109.         if (kvm_arch_create_memslot(&new, npages))
  110.             goto out_free;
  111.     }

  112.     /* Allocate page dirty bitmap if needed */
  113.     if ((new.flags & KVM_MEM_LOG_DIRTY_PAGES) && !new.dirty_bitmap) {
  114.         if (kvm_create_dirty_bitmap(&new) < 0)
  115.             goto out_free;
  116.     }
  117.     // 如果刪除或move內存區域
  118.     if ((change == KVM_MR_DELETE) || (change == KVM_MR_MOVE)) {
  119.         r = -ENOMEM;
  120.         // 複製kvm->memslots的副本
  121.         slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),
  122.                 GFP_KERNEL);
  123.         if (!slots)
  124.             goto out_free;
  125.         slot = id_to_memslot(slots, mem->slot);
  126.         slot->flags |= KVM_MEMSLOT_INVALID;
  127.         // 安裝新memslots,返回舊的memslots
  128.         old_memslots = install_new_memslots(kvm, slots, NULL);

  129.         /* slot was deleted or moved, clear iommu mapping */
  130.         // 原來的slot需要刪除,所以需要unmap掉相應的內存區域
  131.         kvm_iommu_unmap_pages(kvm, &old);
  132.         /* From this point no new shadow pages pointing to a deleted,
  133.          * or moved, memslot will be created.
  134.          *
  135.          * validation of sp->gfn happens in:
  136.          *     - gfn_to_hva (kvm_read_guest, gfn_to_pfn)
  137.          *     - kvm_is_visible_gfn (mmu_check_roots)
  138.          */
  139.         // flush影子頁表中的條目
  140.         kvm_arch_flush_shadow_memslot(kvm, slot);
  141.         slots = old_memslots;
  142.     }
  143.     // 處理private memory slots,對其分配用戶態地址,即HVA
  144.     r = kvm_arch_prepare_memory_region(kvm, &new, mem, change);
  145.     if (r)
  146.         goto out_slots;

  147.     r = -ENOMEM;
  148.     /*
  149.      * We can re-use the old_memslots from above, the only difference
  150.      * from the currently installed memslots is the invalid flag. This
  151.      * will get overwritten by update_memslots anyway.
  152.      */
  153.     if (!slots) {
  154.         slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),
  155.                 GFP_KERNEL);
  156.         if (!slots)
  157.             goto out_free;
  158.     }

  159.     /*
  160.      * IOMMU mapping: New slots need to be mapped. Old slots need to be
  161.      * un-mapped and re-mapped if their base changes. Since base change
  162.      * unmapping is handled above with slot deletion, mapping alone is
  163.      * needed here. Anything else the iommu might care about for existing
  164.      * slots (size changes, userspace addr changes and read-only flag
  165.      * changes) is disallowed above, so any other attribute changes getting
  166.      * here can be skipped.
  167.      */
  168.     if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
  169.         r = kvm_iommu_map_pages(kvm, &new);
  170.         if (r)
  171.             goto out_slots;
  172.     }

  173.     /* actual memory is freed via old in kvm_free_physmem_slot below */
  174.     if (change == KVM_MR_DELETE) {
  175.         new.dirty_bitmap = NULL;
  176.         memset(&new.arch, 0, sizeof(new.arch));
  177.     }
  178.     //將new分配的memslot寫入kvm->memslots[]數組中
  179.     old_memslots = install_new_memslots(kvm, slots, &new);

  180.     kvm_arch_commit_memory_region(kvm, mem, &old, change);
  181.     // 釋放舊內存區域相應的物理內存(HPA)
  182.     kvm_free_physmem_slot(&old, &new);
  183.     kfree(old_memslots);

  184.     return 0;

  185. out_slots:
  186.     kfree(slots);
  187. out_free:
  188.     kvm_free_physmem_slot(&new, &old);
  189. out:
  190.     return r;
  191. }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章