转载请注明:【转载自博客xelatex KVM】,并附本文链接。谢谢。
【注】文章中采用的版本:
Linux-3.11,https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.11.tar.gz
qemu-kvm,git clone http://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git,
git checkout 4d9367b76f71c6d938cf8201392abe4bfb1136cb
先说几个英文缩写:
- GVA - Guest Virtual Address,虚拟机的虚拟地址
- GPA - Guest Physical Address,虚拟机的虚拟地址
- GFN - Guest Frame Number,虚拟机的页框号
- HVA - Host Virtual Address,宿主机虚拟地址,也就是对应Qemu中申请的地址
- HPA - Host Physical Address,宿主机物理地址
- HFN - Host Frame Number,宿主机的页框号
KVM中目前一般采用硬件虚拟化来对内存的MMU进行虚拟化,以提高访存的效率。在比较老的虚拟化技术中,一般采用SPT(Shadow Page Table)来实现MMU虚拟化,所以在KVM代码中有很多地方依然采用类似的命名方式。本文主要针对Intel体系架构Intel VT-x中EPT在KVM中的应用进行说明。
一、内存槽(slot)的注册和管理
上文中我提到KVM只提供机制,不提供策略。为了实现对内存区域的管理,采用了kvm_memory_slot结构来对应Qemu中的AddressSpace。Qemu将虚拟机的线性地址(物理地址)在KVM中注册为多个内存槽,如BIOS、MMIO、GPU、RAW。
Qemu的内存模型请参考官方文档
https://github.com/qemu/qemu/blob/master/docs/memory.txt
。
KVM中memory slot的数据结构:
<include/linux/kvm_host.h>
struct kvm_memory_slot {
gfn_t base_gfn; // 该slot对应虚拟机页框的起点
unsigned long npages; // 该slot中有多少个页
unsigned long *dirty_bitmap; // 脏页的bitmap
struct kvm_arch_memory_slot arch; // 体系结构相关的结构
unsigned long userspace_addr; // 对应HVA的地址
u32 flags; // slot的flag
short id; // slot识别id
};
struct kvm_arch_memory_slot {
unsigned long *rmap[KVM_NR_PAGE_SIZES]; // 反向映射结构(reverse map)
struct kvm_lpage_info *lpage_info[KVM_NR_PAGE_SIZES - 1]; // Large page结构(如2MB、1GB大小页面)
};
KVM数据结构中与内存槽相关的结构,注意KVM对每个虚拟机都会建立和维护一个struct kvm结构。
<include/linux/kvm_host.h>
struct kvm {
spinlock_t mmu_lock; // MMU最大的锁
struct mutex slots_lock; // 内存槽操作锁
struct mm_struct *mm; /* userspace tied to this vm,指向虚拟机内部的页存储结构 */
struct kvm_memslots *memslots; // 存储该KVM所有的memslot
...
};
struct kvm_memslots {
u64 generation;
struct kvm_memory_slot memslots[KVM_MEM_SLOTS_NUM];
/* The mapping table from slot id to the index in memslots[]. */
short id_to_index[KVM_MEM_SLOTS_NUM];
};
kvm->memslots结构在创建虚拟机时被创建,代码见:
<virt/kvm/kvm_main.c>
static struct kvm *kvm_create_vm(unsigned long type)
{
...
kvm->memslots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
if (!kvm->memslots)
goto out_err_nosrcu;
kvm_init_memslots_id(kvm);
...
}
内存槽的注册入口在
kvm_vm_ioctl函数中
case KVM_SET_USER_MEMORY_REGION部分,最终调用函数
__kvm_set_memory_region在KVM中建立与Qemu相对应的内存槽结构。
__kvm_set_memory_region函数主要做了如下几件事情:
- 数据检查
- 调用id_to_memslot来获得kvm->memslots中对应的memslot指针
- 设置memslot的base_gfn、npages等域
- 处理和已经存在的memslots的重叠
- 调用install_new_memslots装载新的memslot
虚拟机线性地址被分成若干个memslot,每个memslot是不能重叠的,也就是说每一段内存区间都必须有独立的作用。一般来说Qemu会对RAM、IO memory、High memory等分别注册若干个memslot
二、KVM MMU创建和初始化流程
2.1 KVM MMU的创建
KVM在vcpu创建时创建和初始化MMU,所以说KVM的MMU是每个VCPU独有的(但是有一些是共享的内容,后面会说到)。创建VCPU的代码起点是函数
kvm_vm_ioctl_create_vcpu
:
<virt/kvm/kvm_main.c>
static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
{
...
vcpu = kvm_arch_vcpu_create(kvm, id);
...
r = kvm_arch_vcpu_setup(vcpu);
...
}
该函数中首先调用
kvm_arch_vcpu_create创建vcpu,然后调用
kvm_arch_vcpu_setup初始化vcpu。在x86架构中,kvm_arch_vcpu_create最终调用vmx_create_vcpu函数进行VCPU的创建工作。MMU的创建在
vmx_create_vcpu => kvm_vcpu_init => kvm_arch_vcpu_init => kvm_mmu_create 中,如下: